Merge remote-tracking branch 'refs/remotes/origin/master' into 2.8-release
|
|
@ -32,3 +32,7 @@ The body should provide a meaningful commit message.
|
||||||
1. Write tests for your code (if possible)
|
1. Write tests for your code (if possible)
|
||||||
|
|
||||||
1. Make sure the Wiki pages don't become stale by updating them (if needed)
|
1. Make sure the Wiki pages don't become stale by updating them (if needed)
|
||||||
|
|
||||||
|
### Further reading
|
||||||
|
|
||||||
|
* [Importance of good commit messages](https://blog.oozou.com/commit-messages-matter-60309983c227?gi=c550a10d0f67)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,18 @@
|
||||||
_Before creating an issue, please search the existing issues to see if a similar one has already been created. You can search issues by specific labels (e.g. `label:nearby `) or just by typing keywords into the search filter._
|
|
||||||
|
|
||||||
**Summary:**
|
**Summary:**
|
||||||
|
|
||||||
Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
|
Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
|
||||||
|
|
||||||
|
_Before creating an issue, please search the existing issues to see if a similar one has already been created. You can search issues by specific labels (e.g. `label:nearby `) or just by typing keywords into the search filter._
|
||||||
|
|
||||||
**Steps to reproduce:**
|
**Steps to reproduce:**
|
||||||
|
|
||||||
How can we reproduce the issue?
|
How can we reproduce the issue?
|
||||||
|
What did you expect the app to do, and what did you see instead?
|
||||||
|
|
||||||
**Add System logs:**
|
**Add System logs:**
|
||||||
|
|
||||||
Add logcat files here (if possible).
|
Add logcat files here (if possible).
|
||||||
|
|
||||||
**Expected behavior:**
|
|
||||||
|
|
||||||
What did you expect the App to do?
|
|
||||||
|
|
||||||
**Observed behavior:**
|
|
||||||
|
|
||||||
What did you see instead? Describe your issue in detail here.
|
|
||||||
|
|
||||||
**Device and Android version:**
|
**Device and Android version:**
|
||||||
|
|
||||||
What make and model device (e.g., Samsung J7) did you encounter this on? What Android
|
What make and model device (e.g., Samsung J7) did you encounter this on? What Android
|
||||||
|
|
@ -28,7 +21,7 @@ version (e.g., Android 4.0 Ice Cream Sandwich or Android 6.0 Marshmallow) are yo
|
||||||
|
|
||||||
**Commons app version:**
|
**Commons app version:**
|
||||||
|
|
||||||
You can find this information by going to the navigation drawer in the app and tapping 'About'
|
You can find this information by going to the navigation drawer in the app and tapping 'About'. If you are building from our codebase instead of downloading the app, please also mention the branch and build variant (e.g. master and prodDebug).
|
||||||
|
|
||||||
**Screen-shots:**
|
**Screen-shots:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ apply from: 'quality.gradle'
|
||||||
apply plugin: 'com.getkeepsafe.dexcount'
|
apply plugin: 'com.getkeepsafe.dexcount'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
implementation 'com.prof.rssparser:rssparser:1.1'
|
||||||
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
||||||
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||||
implementation 'in.yuvi:http.fluent:1.3'
|
implementation 'in.yuvi:http.fluent:1.3'
|
||||||
|
|
@ -19,54 +21,46 @@ dependencies {
|
||||||
implementation 'com.jakewharton.timber:timber:4.5.1'
|
implementation 'com.jakewharton.timber:timber:4.5.1'
|
||||||
implementation 'info.debatty:java-string-similarity:0.24'
|
implementation 'info.debatty:java-string-similarity:0.24'
|
||||||
implementation 'com.borjabravo:readmoretextview:2.1.0'
|
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.0'
|
||||||
implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.0@aar') {
|
implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.0@aar') {
|
||||||
transitive = true
|
transitive = true
|
||||||
}
|
}
|
||||||
|
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
|
||||||
implementation "com.github.deano2390:MaterialShowcaseView:1.2.0"
|
//noinspection GradleCompatible
|
||||||
|
|
||||||
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
|
||||||
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
|
||||||
implementation "com.android.support:design:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:design:$SUPPORT_LIB_VERSION"
|
||||||
implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION"
|
||||||
|
|
||||||
implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
|
||||||
|
|
||||||
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||||
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
|
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
|
||||||
implementation 'com.squareup.okio:okio:1.13.0'
|
implementation 'com.squareup.okio:okio:1.13.0'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
||||||
// Because RxAndroid releases are few and far between, it is recommended you also
|
// 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.
|
// explicitly depend on RxJava's latest version for bug fixes and new features.
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
|
implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4: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-appcompat-v7:2.0.0'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
|
||||||
|
|
||||||
implementation 'org.jsoup:jsoup:1.11.3'
|
implementation 'org.jsoup:jsoup:1.11.3'
|
||||||
|
|
||||||
implementation 'com.facebook.fresco:fresco:1.5.0'
|
implementation 'com.facebook.fresco:fresco:1.5.0'
|
||||||
implementation 'com.facebook.stetho:stetho:1.5.0'
|
implementation 'com.facebook.stetho:stetho:1.5.0'
|
||||||
|
|
||||||
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
|
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
|
||||||
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
|
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
|
||||||
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
testImplementation 'org.robolectric:multidex:3.4.2'
|
||||||
testImplementation "org.robolectric:multidex:3.4.2"
|
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.robolectric:robolectric:3.7.1'
|
testImplementation 'org.robolectric:robolectric:3.7.1'
|
||||||
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
|
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||||
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
||||||
|
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
||||||
|
|
||||||
implementation 'com.caverock:androidsvg:1.2.1'
|
implementation 'com.caverock:androidsvg:1.2.1'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.7.1'
|
implementation 'com.github.bumptech.glide:glide:4.7.1'
|
||||||
|
|
@ -82,6 +76,9 @@ dependencies {
|
||||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY"
|
debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY"
|
||||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
||||||
testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
||||||
|
|
||||||
|
implementation 'com.borjabravo:readmoretextview:2.1.0'
|
||||||
|
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -134,6 +131,7 @@ android {
|
||||||
flavorDimensions 'tier'
|
flavorDimensions 'tier'
|
||||||
productFlavors {
|
productFlavors {
|
||||||
prod {
|
prod {
|
||||||
|
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
||||||
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
|
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
||||||
|
|
@ -151,6 +149,7 @@ android {
|
||||||
|
|
||||||
beta {
|
beta {
|
||||||
// What values do we need to hit the BETA versions of the site / api ?
|
// What values do we need to hit the BETA versions of the site / api ?
|
||||||
|
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
||||||
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
|
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
||||||
|
|
|
||||||
BIN
app/libs/java-json.jar
Normal file
|
|
@ -1,3 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="fr.free.nrw.commons">
|
package="fr.free.nrw.commons">
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@
|
||||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||||
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
||||||
|
|
||||||
|
|
||||||
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
|
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
|
||||||
<uses-feature android:name="android.hardware.location.gps" />
|
<uses-feature android:name="android.hardware.location.gps" />
|
||||||
|
|
||||||
|
|
@ -35,6 +37,7 @@
|
||||||
<activity android:name=".auth.LoginActivity">
|
<activity android:name=".auth.LoginActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
@ -45,7 +48,7 @@
|
||||||
android:name=".upload.ShareActivity"
|
android:name=".upload.ShareActivity"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter android:label="@string/intent_share_upload_label">
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="image/*" />
|
<data android:mimeType="image/*" />
|
||||||
|
|
@ -57,7 +60,7 @@
|
||||||
android:name=".upload.MultipleShareActivity"
|
android:name=".upload.MultipleShareActivity"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter android:label="@string/intent_share_upload_label">
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="image/*" />
|
<data android:mimeType="image/*" />
|
||||||
|
|
@ -92,13 +95,33 @@
|
||||||
android:name=".notification.NotificationActivity"
|
android:name=".notification.NotificationActivity"
|
||||||
android:label="@string/navigation_item_notification" />
|
android:label="@string/navigation_item_notification" />
|
||||||
|
|
||||||
|
<activity android:name=".quiz.QuizActivity"
|
||||||
|
android:label="@string/quiz"/>
|
||||||
|
|
||||||
|
<activity android:name=".quiz.QuizResultActivity"
|
||||||
|
android:label="@string/result"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".category.CategoryImagesActivity"
|
android:name=".category.CategoryImagesActivity"
|
||||||
android:label="@string/title_activity_featured_images"
|
android:label="@string/title_activity_featured_images"
|
||||||
android:parentActivityName=".contributions.ContributionsActivity" />
|
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||||
|
|
||||||
<service android:name=".upload.UploadService" />
|
<activity
|
||||||
|
android:name=".category.CategoryDetailsActivity"
|
||||||
|
android:label="@string/title_activity_featured_images"
|
||||||
|
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".explore.SearchActivity"
|
||||||
|
android:label="@string/title_activity_search"
|
||||||
|
android:parentActivityName=".contributions.ContributionsActivity"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".achievements.AchievementsActivity"
|
||||||
|
android:label="@string/Achievements" />
|
||||||
|
|
||||||
|
<service android:name=".upload.UploadService" />
|
||||||
<service
|
<service
|
||||||
android:name=".auth.WikiAccountAuthenticatorService"
|
android:name=".auth.WikiAccountAuthenticatorService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|
@ -106,7 +129,6 @@
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.accounts.AccountAuthenticator" />
|
<action android:name="android.accounts.AccountAuthenticator" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.accounts.AccountAuthenticator"
|
android:name="android.accounts.AccountAuthenticator"
|
||||||
android:resource="@xml/authenticator" />
|
android:resource="@xml/authenticator" />
|
||||||
|
|
@ -165,6 +187,23 @@
|
||||||
android:label="@string/provider_categories"
|
android:label="@string/provider_categories"
|
||||||
android:syncable="false" />
|
android:syncable="false" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name=".explore.recentsearches.RecentSearchesContentProvider"
|
||||||
|
android:authorities="fr.free.nrw.commons.explore.recentsearches.contentprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/provider_searches"
|
||||||
|
android:syncable="false" />
|
||||||
|
|
||||||
|
<receiver android:name=".widget.PicOfDayAppWidget">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/pic_of_day_app_widget_info" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -9,9 +9,6 @@ import android.os.Bundle;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
import android.util.Log;
|
|
||||||
import android.support.customtabs.CustomTabsIntent;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
@ -20,7 +17,6 @@ import android.widget.ArrayAdapter;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
@ -28,8 +24,6 @@ import butterknife.OnClick;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||||
|
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents about screen of this app
|
* Represents about screen of this app
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import java.io.File;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.category.CategoryDao;
|
import fr.free.nrw.commons.category.CategoryDao;
|
||||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,17 @@ package fr.free.nrw.commons;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
@ -356,12 +360,8 @@ public class Media implements Parcelable {
|
||||||
* @param descriptions Media descriptions
|
* @param descriptions Media descriptions
|
||||||
*/
|
*/
|
||||||
void setDescriptions(Map<String, String> descriptions) {
|
void setDescriptions(Map<String, String> descriptions) {
|
||||||
for (String key : this.descriptions.keySet()) {
|
this.descriptions.clear();
|
||||||
this.descriptions.remove(key);
|
this.descriptions.putAll(descriptions);
|
||||||
}
|
|
||||||
for (String key : descriptions.keySet()) {
|
|
||||||
this.descriptions.put(key, descriptions.get(key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,16 @@ public class MediaWikiImageView extends SimpleDraweeView {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thumbnailUrlCache.get(media.getFilename()) != null) {
|
if(media.getFilename() != null) {
|
||||||
setImageUrl(thumbnailUrlCache.get(media.getFilename()));
|
if (thumbnailUrlCache.get(media.getFilename()) != null) {
|
||||||
} else {
|
setImageUrl(thumbnailUrlCache.get(media.getFilename()));
|
||||||
setImageUrl(null);
|
} else {
|
||||||
|
setImageUrl(null);
|
||||||
|
currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
|
||||||
|
currentThumbnailTask.execute(media.getFilename());
|
||||||
|
}
|
||||||
|
} else { // local image
|
||||||
|
setImageUrl(media.getLocalUri().toString());
|
||||||
currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
|
currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
|
||||||
currentThumbnailTask.execute(media.getFilename());
|
currentThumbnailTask.execute(media.getFilename());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package fr.free.nrw.commons;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.customtabs.CustomTabsIntent;
|
import android.support.customtabs.CustomTabsIntent;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Hex;
|
import org.apache.commons.codec.binary.Hex;
|
||||||
|
|
@ -76,7 +78,11 @@ public class Utils {
|
||||||
* @return string with capitalized first character
|
* @return string with capitalized first character
|
||||||
*/
|
*/
|
||||||
public static String capitalize(String string) {
|
public static String capitalize(String string) {
|
||||||
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
|
if(string.length() > 0) {
|
||||||
|
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
|
||||||
|
} else {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -146,7 +152,7 @@ public class Utils {
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String[] command = new String[] {"logcat","-d","-v","threadtime"};
|
String[] command = new String[]{"logcat","-d","-v","threadtime"};
|
||||||
|
|
||||||
Process process = Runtime.getRuntime().exec(command);
|
Process process = Runtime.getRuntime().exec(command);
|
||||||
|
|
||||||
|
|
@ -195,4 +201,18 @@ public class Utils {
|
||||||
customTabsIntent.launchUrl(context, url);
|
customTabsIntent.launchUrl(context, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To take screenshot of the screen and return it in Bitmap format
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Bitmap getScreenShot(View view) {
|
||||||
|
View screenView = view.getRootView();
|
||||||
|
screenView.setDrawingCacheEnabled(true);
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(screenView.getDrawingCache());
|
||||||
|
screenView.setDrawingCacheEnabled(false);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import com.viewpagerindicator.CirclePageIndicator;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.quiz.QuizActivity;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.BaseActivity;
|
||||||
|
|
||||||
public class WelcomeActivity extends BaseActivity {
|
public class WelcomeActivity extends BaseActivity {
|
||||||
|
|
@ -17,6 +18,7 @@ public class WelcomeActivity extends BaseActivity {
|
||||||
@BindView(R.id.welcomePagerIndicator) CirclePageIndicator indicator;
|
@BindView(R.id.welcomePagerIndicator) CirclePageIndicator indicator;
|
||||||
|
|
||||||
private WelcomePagerAdapter adapter = new WelcomePagerAdapter();
|
private WelcomePagerAdapter adapter = new WelcomePagerAdapter();
|
||||||
|
private boolean isQuiz;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises exiting fields and dependencies
|
* Initialises exiting fields and dependencies
|
||||||
|
|
@ -28,6 +30,15 @@ public class WelcomeActivity extends BaseActivity {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_welcome);
|
setContentView(R.layout.activity_welcome);
|
||||||
|
|
||||||
|
if(getIntent() != null) {
|
||||||
|
Bundle bundle = getIntent().getExtras();
|
||||||
|
if (bundle != null) {
|
||||||
|
isQuiz = bundle.getBoolean("isQuiz");
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
isQuiz = false;
|
||||||
|
}
|
||||||
|
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
pager.setAdapter(adapter);
|
pager.setAdapter(adapter);
|
||||||
|
|
@ -40,6 +51,10 @@ public class WelcomeActivity extends BaseActivity {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
if(isQuiz){
|
||||||
|
Intent i = new Intent(WelcomeActivity.this, QuizActivity.class);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
adapter.setCallback(null);
|
adapter.setCallback(null);
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
package fr.free.nrw.commons.achievements;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* represnts Achievements class ans stores all the parameters
|
||||||
|
*/
|
||||||
|
public class Achievements {
|
||||||
|
private int uniqueUsedImages;
|
||||||
|
private int articlesUsingImages;
|
||||||
|
private int thanksReceived;
|
||||||
|
private int imagesEditedBySomeoneElse;
|
||||||
|
private int featuredImages;
|
||||||
|
private int imagesUploaded;
|
||||||
|
private int revertCount;
|
||||||
|
|
||||||
|
public Achievements(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constructor for achievements class to set its data members
|
||||||
|
* @param uniqueUsedImages
|
||||||
|
* @param articlesUsingImages
|
||||||
|
* @param thanksReceived
|
||||||
|
* @param imagesEditedBySomeoneElse
|
||||||
|
* @param featuredImages
|
||||||
|
* @param imagesUploaded
|
||||||
|
* @param revertCount
|
||||||
|
*/
|
||||||
|
public Achievements(int uniqueUsedImages,
|
||||||
|
int articlesUsingImages,
|
||||||
|
int thanksReceived,
|
||||||
|
int imagesEditedBySomeoneElse,
|
||||||
|
int featuredImages,
|
||||||
|
int imagesUploaded,
|
||||||
|
int revertCount) {
|
||||||
|
this.uniqueUsedImages = uniqueUsedImages;
|
||||||
|
this.articlesUsingImages = articlesUsingImages;
|
||||||
|
this.thanksReceived = thanksReceived;
|
||||||
|
this.imagesEditedBySomeoneElse = imagesEditedBySomeoneElse;
|
||||||
|
this.featuredImages = featuredImages;
|
||||||
|
this.imagesUploaded = imagesUploaded;
|
||||||
|
this.revertCount = revertCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for Achievements class
|
||||||
|
*/
|
||||||
|
public class AchievementsBuilder {
|
||||||
|
private int nestedUniqueUsedImages;
|
||||||
|
private int nestedArticlesUsingImages;
|
||||||
|
private int nestedThanksReceived;
|
||||||
|
private int nestedImagesEditedBySomeoneElse;
|
||||||
|
private int nestedFeaturedImages;
|
||||||
|
private int nestedImagesUploaded;
|
||||||
|
private int nestedRevertCount;
|
||||||
|
|
||||||
|
public AchievementsBuilder setUniqueUsedImages(int uniqueUsedImages) {
|
||||||
|
this.nestedUniqueUsedImages = uniqueUsedImages;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AchievementsBuilder setArticlesUsingImages(int articlesUsingImages) {
|
||||||
|
this.nestedArticlesUsingImages = articlesUsingImages;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AchievementsBuilder setThanksReceived(int thanksReceived) {
|
||||||
|
this.nestedThanksReceived = thanksReceived;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AchievementsBuilder setImagesEditedBySomeoneElse(int imagesEditedBySomeoneElse) {
|
||||||
|
this.nestedImagesEditedBySomeoneElse = imagesEditedBySomeoneElse;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AchievementsBuilder setFeaturedImages(int featuredImages) {
|
||||||
|
this.nestedFeaturedImages = featuredImages;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AchievementsBuilder setImagesUploaded(int imagesUploaded) {
|
||||||
|
this.nestedImagesUploaded = imagesUploaded;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AchievementsBuilder setRevertCount( int revertCount){
|
||||||
|
this.nestedRevertCount = revertCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Achievements createAchievements(){
|
||||||
|
return new Achievements(nestedUniqueUsedImages,
|
||||||
|
nestedArticlesUsingImages,
|
||||||
|
nestedThanksReceived,
|
||||||
|
nestedImagesEditedBySomeoneElse,
|
||||||
|
nestedFeaturedImages,
|
||||||
|
nestedImagesUploaded,
|
||||||
|
nestedRevertCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getter function to get count of images uploaded
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getImagesUploaded() {
|
||||||
|
return imagesUploaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getter function to get count of featured images
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getFeaturedImages() {
|
||||||
|
return featuredImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getter function to get count of thanks received
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getThanksReceived() {
|
||||||
|
return thanksReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getter function to get count of unique images used by wiki
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getUniqueUsedImages() {
|
||||||
|
return uniqueUsedImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setter function to count of images uploaded
|
||||||
|
* @param imagesUploaded
|
||||||
|
*/
|
||||||
|
public void setImagesUploaded(int imagesUploaded) {
|
||||||
|
this.imagesUploaded = imagesUploaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setter function to set count of featured images
|
||||||
|
* @param featuredImages
|
||||||
|
*/
|
||||||
|
public void setFeaturedImages(int featuredImages) {
|
||||||
|
this.featuredImages = featuredImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setter function to set the count of images edited by someone
|
||||||
|
* @param imagesEditedBySomeoneElse
|
||||||
|
*/
|
||||||
|
public void setImagesEditedBySomeoneElse(int imagesEditedBySomeoneElse) {
|
||||||
|
this.imagesEditedBySomeoneElse = imagesEditedBySomeoneElse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setter function to set count of thanks received
|
||||||
|
* @param thanksReceived
|
||||||
|
*/
|
||||||
|
public void setThanksReceived(int thanksReceived) {
|
||||||
|
this.thanksReceived = thanksReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setter function to count of articles using images uploaded
|
||||||
|
* @param articlesUsingImages
|
||||||
|
*/
|
||||||
|
public void setArticlesUsingImages(int articlesUsingImages) {
|
||||||
|
this.articlesUsingImages = articlesUsingImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setter function to set count of uniques images used by wiki
|
||||||
|
* @param uniqueUsedImages
|
||||||
|
*/
|
||||||
|
public void setUniqueUsedImages(int uniqueUsedImages) {
|
||||||
|
this.uniqueUsedImages = uniqueUsedImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to set count of images reverted
|
||||||
|
* @param revertCount
|
||||||
|
*/
|
||||||
|
public void setRevertCount(int revertCount) {
|
||||||
|
this.revertCount = revertCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to calculate the percentages of images that haven't been reverted
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getNotRevertPercentage(){
|
||||||
|
try {
|
||||||
|
return ((imagesUploaded - revertCount) * 100)/imagesUploaded;
|
||||||
|
} catch (ArithmeticException divideByZero ){
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,458 @@
|
||||||
|
package fr.free.nrw.commons.achievements;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.content.res.ResourcesCompat;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.dinuscxj.progressbar.CircleProgressBar;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* activity for sharing feedback on uploaded activity
|
||||||
|
*/
|
||||||
|
public class AchievementsActivity extends NavigationBaseActivity {
|
||||||
|
|
||||||
|
private static final double BADGE_IMAGE_WIDTH_RATIO = 0.4;
|
||||||
|
private static final double BADGE_IMAGE_HEIGHT_RATIO = 0.3;
|
||||||
|
private Boolean isUploadFetched = false;
|
||||||
|
private Boolean isStatisticsFetched = false;
|
||||||
|
private Boolean isRevertFetched = false;
|
||||||
|
private Achievements achievements = new Achievements();
|
||||||
|
private LevelController.LevelInfo levelInfo;
|
||||||
|
|
||||||
|
@BindView(R.id.achievement_badge)
|
||||||
|
ImageView imageView;
|
||||||
|
@BindView(R.id.achievement_level)
|
||||||
|
TextView levelNumber;
|
||||||
|
@BindView(R.id.toolbar)
|
||||||
|
Toolbar toolbar;
|
||||||
|
@BindView(R.id.thanks_received)
|
||||||
|
TextView thanksReceived;
|
||||||
|
@BindView(R.id.images_uploaded_progressbar)
|
||||||
|
CircleProgressBar imagesUploadedProgressbar;
|
||||||
|
@BindView(R.id.images_used_by_wiki_progressbar)
|
||||||
|
CircleProgressBar imagesUsedByWikiProgessbar;
|
||||||
|
@BindView(R.id.image_reverts_progressbar)
|
||||||
|
CircleProgressBar imageRevertsProgressbar;
|
||||||
|
@BindView(R.id.image_featured)
|
||||||
|
TextView imagesFeatured;
|
||||||
|
@BindView(R.id.images_revert_limit_text)
|
||||||
|
TextView imagesRevertLimitText;
|
||||||
|
@BindView(R.id.progressBar)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
@BindView(R.id.layout_image_uploaded)
|
||||||
|
RelativeLayout layoutImageUploaded;
|
||||||
|
@BindView(R.id.layout_image_reverts)
|
||||||
|
RelativeLayout layoutImageReverts;
|
||||||
|
@BindView(R.id.layout_image_used_by_wiki)
|
||||||
|
RelativeLayout layoutImageUsedByWiki;
|
||||||
|
@BindView(R.id.layout_statistics)
|
||||||
|
LinearLayout layoutStatistics;
|
||||||
|
@Inject
|
||||||
|
SessionManager sessionManager;
|
||||||
|
@Inject
|
||||||
|
MediaWikiApi mediaWikiApi;
|
||||||
|
|
||||||
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method helps in the creation Achievement screen and
|
||||||
|
* dynamically set the size of imageView
|
||||||
|
*
|
||||||
|
* @param savedInstanceState Data bundle
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressLint("StringFormatInvalid")
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_achievements);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
/**
|
||||||
|
* DisplayMetrics used to fetch the size of the screen
|
||||||
|
*/
|
||||||
|
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||||
|
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||||
|
int height = displayMetrics.heightPixels;
|
||||||
|
int width = displayMetrics.widthPixels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for the setting the size of imageView at runtime
|
||||||
|
*/
|
||||||
|
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)
|
||||||
|
imageView.getLayoutParams();
|
||||||
|
params.height = (int) (height * BADGE_IMAGE_HEIGHT_RATIO);
|
||||||
|
params.width = (int) (width * BADGE_IMAGE_WIDTH_RATIO);
|
||||||
|
imageView.setImageResource(R.drawable.badge);
|
||||||
|
imageView.requestLayout();
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
hideLayouts();
|
||||||
|
setAchievements();
|
||||||
|
setUploadCount();
|
||||||
|
setRevertCount();
|
||||||
|
initDrawer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to invoke the AlertDialog on clicking info button
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.achievement_info)
|
||||||
|
public void showInfoDialog(){
|
||||||
|
launchAlert(getResources().getString(R.string.Achievements)
|
||||||
|
,getResources().getString(R.string.achievements_info_message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
getMenuInflater().inflate(R.menu.menu_about, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == R.id.share_app_icon) {
|
||||||
|
View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||||
|
Bitmap screenShot = Utils.getScreenShot(rootView);
|
||||||
|
showAlert(screenShot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To take bitmap and store it temporary storage and share it
|
||||||
|
*
|
||||||
|
* @param bitmap
|
||||||
|
*/
|
||||||
|
void shareScreen(Bitmap bitmap) {
|
||||||
|
try {
|
||||||
|
File file = new File(this.getExternalCacheDir(), "screen.png");
|
||||||
|
FileOutputStream fOut = new FileOutputStream(file);
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
|
||||||
|
fOut.flush();
|
||||||
|
fOut.close();
|
||||||
|
file.setReadable(true, false);
|
||||||
|
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
|
||||||
|
intent.setType("image/png");
|
||||||
|
startActivity(Intent.createChooser(intent, "Share image via"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
//Do Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To call the API to get results in form Single<JSONObject>
|
||||||
|
* which then calls parseJson when results are fetched
|
||||||
|
*/
|
||||||
|
private void setAchievements() {
|
||||||
|
if(checkAccount()) {
|
||||||
|
compositeDisposable.add(mediaWikiApi
|
||||||
|
.getAchievements(sessionManager.getCurrentAccount().name)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
jsonObject -> parseJson(jsonObject),
|
||||||
|
t -> Timber.e(t, "Fetching achievements statisticss failed")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To call the API to get reverts count in form of JSONObject
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
private void setRevertCount(){
|
||||||
|
if(checkAccount()) {
|
||||||
|
compositeDisposable.add(mediaWikiApi
|
||||||
|
.getRevertRespObjectSingle(sessionManager.getCurrentAccount().name)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
object -> parseJsonRevertCount(object),
|
||||||
|
t -> Timber.e(t, "Fetching revert count failed")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to set number of deleted images
|
||||||
|
* @param object
|
||||||
|
*/
|
||||||
|
private void parseJsonRevertCount(JSONObject object){
|
||||||
|
try {
|
||||||
|
achievements.setRevertCount(object.getInt("deletedUploads"));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Timber.d( e, e.getMessage());
|
||||||
|
}
|
||||||
|
isRevertFetched = true;
|
||||||
|
hideProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to the count of images uploaded by user
|
||||||
|
*/
|
||||||
|
private void setUploadCount() {
|
||||||
|
if(checkAccount()) {
|
||||||
|
compositeDisposable.add(mediaWikiApi
|
||||||
|
.getUploadCount(sessionManager.getCurrentAccount().name)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
uploadCount -> setAchievementsUploadCount(uploadCount),
|
||||||
|
t -> Timber.e(t, "Fetching upload count failed")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to set achievements upload count and call hideProgressbar
|
||||||
|
* @param uploadCount
|
||||||
|
*/
|
||||||
|
private void setAchievementsUploadCount(int uploadCount){
|
||||||
|
achievements.setImagesUploaded(uploadCount);
|
||||||
|
isUploadFetched = true;
|
||||||
|
hideProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to the uploaded images progressbar
|
||||||
|
* @param uploadCount
|
||||||
|
*/
|
||||||
|
private void setUploadProgress(int uploadCount){
|
||||||
|
imagesUploadedProgressbar.setProgress
|
||||||
|
(100*uploadCount/levelInfo.getMaxUploadCount());
|
||||||
|
imagesUploadedProgressbar.setProgressTextFormatPattern
|
||||||
|
(uploadCount +"/" + levelInfo.getMaxUploadCount() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to set the non revert image percentage
|
||||||
|
* @param notRevertPercentage
|
||||||
|
*/
|
||||||
|
private void setImageRevertPercentage(int notRevertPercentage){
|
||||||
|
imageRevertsProgressbar.setProgress(notRevertPercentage);
|
||||||
|
String revertPercentage = Integer.toString(notRevertPercentage);
|
||||||
|
imageRevertsProgressbar.setProgressTextFormatPattern(revertPercentage + "%%");
|
||||||
|
imagesRevertLimitText.setText(getResources().getString(R.string.achievements_revert_limit_message)+ levelInfo.getMinNonRevertPercentage() + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to parse the JSONObject containing results
|
||||||
|
* @param object
|
||||||
|
*/
|
||||||
|
private void parseJson(JSONObject object) {
|
||||||
|
try {
|
||||||
|
achievements.setUniqueUsedImages(object.getInt("uniqueUsedImages"));
|
||||||
|
achievements.setArticlesUsingImages(object.getInt("articlesUsingImages"));
|
||||||
|
achievements.setThanksReceived(object.getInt("thanksReceived"));
|
||||||
|
achievements.setImagesEditedBySomeoneElse(object.getInt("imagesEditedBySomeoneElse"));
|
||||||
|
JSONObject featuredImages = object.getJSONObject("featuredImages");
|
||||||
|
achievements.setFeaturedImages
|
||||||
|
(featuredImages.getInt("Quality_images") +
|
||||||
|
featuredImages.getInt("Featured_pictures_on_Wikimedia_Commons"));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
isStatisticsFetched = true;
|
||||||
|
hideProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used the inflate the fetched statistics of the images uploaded by user
|
||||||
|
* and assign badge and level
|
||||||
|
* @param achievements
|
||||||
|
*/
|
||||||
|
private void inflateAchievements(Achievements achievements ){
|
||||||
|
thanksReceived.setText(Integer.toString(achievements.getThanksReceived()));
|
||||||
|
imagesUsedByWikiProgessbar.setProgress
|
||||||
|
(100*achievements.getUniqueUsedImages()/levelInfo.getMaxUniqueImages() );
|
||||||
|
imagesUsedByWikiProgessbar.setProgressTextFormatPattern
|
||||||
|
(achievements.getUniqueUsedImages() + "/" + levelInfo.getMaxUniqueImages());
|
||||||
|
imagesFeatured.setText(Integer.toString(achievements.getFeaturedImages()));
|
||||||
|
String levelUpInfoString = getString(R.string.level);
|
||||||
|
levelUpInfoString += " " + Integer.toString(levelInfo.getLevelNumber());
|
||||||
|
levelNumber.setText(levelUpInfoString);
|
||||||
|
final ContextThemeWrapper wrapper = new ContextThemeWrapper(this, levelInfo.getLevelStyle());
|
||||||
|
Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.badge, wrapper.getTheme());
|
||||||
|
Bitmap bitmap = BitmapUtils.drawableToBitmap(drawable);
|
||||||
|
BitmapDrawable bitmapImage = BitmapUtils.writeOnDrawable(bitmap, Integer.toString(levelInfo.getLevelNumber()),this);
|
||||||
|
imageView.setImageDrawable(bitmapImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a way to change current activity to AchievementActivity
|
||||||
|
* @param context
|
||||||
|
*/
|
||||||
|
public static void startYourself(Context context) {
|
||||||
|
Intent intent = new Intent(context, AchievementsActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to hide progressbar
|
||||||
|
*/
|
||||||
|
private void hideProgressBar() {
|
||||||
|
if (progressBar != null && isUploadFetched && isStatisticsFetched && isRevertFetched) {
|
||||||
|
levelInfo = LevelController.LevelInfo.from(achievements.getImagesUploaded(),
|
||||||
|
achievements.getUniqueUsedImages(),
|
||||||
|
achievements.getNotRevertPercentage());
|
||||||
|
inflateAchievements(achievements);
|
||||||
|
setUploadProgress(achievements.getImagesUploaded());
|
||||||
|
setImageRevertPercentage(achievements.getNotRevertPercentage());
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
layoutImageReverts.setVisibility(View.VISIBLE);
|
||||||
|
layoutImageUploaded.setVisibility(View.VISIBLE);
|
||||||
|
layoutImageUsedByWiki.setVisibility(View.VISIBLE);
|
||||||
|
layoutStatistics.setVisibility(View.VISIBLE);
|
||||||
|
imageView.setVisibility(View.VISIBLE);
|
||||||
|
levelNumber.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to hide the layouts while fetching results from api
|
||||||
|
*/
|
||||||
|
private void hideLayouts(){
|
||||||
|
layoutImageUsedByWiki.setVisibility(View.INVISIBLE);
|
||||||
|
layoutImageUploaded.setVisibility(View.INVISIBLE);
|
||||||
|
layoutImageReverts.setVisibility(View.INVISIBLE);
|
||||||
|
layoutStatistics.setVisibility(View.INVISIBLE);
|
||||||
|
imageView.setVisibility(View.INVISIBLE);
|
||||||
|
levelNumber.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It display the alertDialog with Image of screenshot
|
||||||
|
* @param screenshot
|
||||||
|
*/
|
||||||
|
public void showAlert(Bitmap screenshot){
|
||||||
|
AlertDialog.Builder alertadd = new AlertDialog.Builder(AchievementsActivity.this);
|
||||||
|
LayoutInflater factory = LayoutInflater.from(AchievementsActivity.this);
|
||||||
|
final View view = factory.inflate(R.layout.image_alert_layout, null);
|
||||||
|
ImageView screenShotImage = (ImageView) view.findViewById(R.id.alert_image);
|
||||||
|
screenShotImage.setImageBitmap(screenshot);
|
||||||
|
TextView shareMessage = (TextView) view.findViewById(R.id.alert_text);
|
||||||
|
shareMessage.setText(R.string.achievements_share_message);
|
||||||
|
alertadd.setView(view);
|
||||||
|
alertadd.setPositiveButton("Proceed", new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
shareScreen(screenshot);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alertadd.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alertadd.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.images_upload_info)
|
||||||
|
public void showUploadInfo(){
|
||||||
|
launchAlert(getResources().getString(R.string.images_uploaded)
|
||||||
|
,getResources().getString(R.string.images_uploaded_explanation));
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.images_reverted_info)
|
||||||
|
public void showRevertedInfo(){
|
||||||
|
launchAlert(getResources().getString(R.string.image_reverts)
|
||||||
|
,getResources().getString(R.string.images_reverted_explanation));
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.images_used_by_wiki_info)
|
||||||
|
public void showUsedByWikiInfo(){
|
||||||
|
launchAlert(getResources().getString(R.string.images_used_by_wiki)
|
||||||
|
,getResources().getString(R.string.images_used_explanation));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* takes title and message as input to display alerts
|
||||||
|
* @param title
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
private void launchAlert(String title, String message){
|
||||||
|
new AlertDialog.Builder(AchievementsActivity.this)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check to ensure that user is logged in
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean checkAccount(){
|
||||||
|
Account currentAccount = sessionManager.getCurrentAccount();
|
||||||
|
if(currentAccount == null) {
|
||||||
|
Timber.d("Current account is null");
|
||||||
|
ViewUtil.showLongToast(this, getResources().getString(R.string.user_not_logged_in));
|
||||||
|
sessionManager.forceLogin(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package fr.free.nrw.commons.achievements;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
public class BitmapUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* write level Number on the badge
|
||||||
|
* @param bm
|
||||||
|
* @param text
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static BitmapDrawable writeOnDrawable(Bitmap bm, String text, Context context){
|
||||||
|
Bitmap.Config config = bm.getConfig();
|
||||||
|
if(config == null){
|
||||||
|
config = Bitmap.Config.ARGB_8888;
|
||||||
|
}
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),config);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
canvas.drawBitmap(bm, 0, 0, null);
|
||||||
|
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
paint.setColor(Color.WHITE);
|
||||||
|
paint.setTextSize(Math.round(canvas.getHeight()/2));
|
||||||
|
paint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
Rect rectText = new Rect();
|
||||||
|
paint.getTextBounds(text,0, text.length(),rectText);
|
||||||
|
canvas.drawText(text, Math.round(canvas.getWidth()/2),Math.round(canvas.getHeight()/1.35), paint);
|
||||||
|
return new BitmapDrawable(context.getResources(), bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Drawable to bitmap
|
||||||
|
* @param drawable
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Bitmap drawableToBitmap (Drawable drawable) {
|
||||||
|
if (drawable instanceof BitmapDrawable) {
|
||||||
|
return ((BitmapDrawable)drawable).getBitmap();
|
||||||
|
}
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||||
|
drawable.draw(canvas);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package fr.free.nrw.commons.achievements;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculates the level of the user
|
||||||
|
*/
|
||||||
|
public class LevelController {
|
||||||
|
|
||||||
|
public LevelInfo level;
|
||||||
|
public enum LevelInfo{
|
||||||
|
LEVEL_1(1, R.style.LevelOne, 5, 20, 85),
|
||||||
|
LEVEL_2(2, R.style.LevelTwo, 10, 30, 86),
|
||||||
|
LEVEL_3(3, R.style.LevelThree, 15,40, 87),
|
||||||
|
LEVEL_4(4, R.style.LevelFour,20,50, 88),
|
||||||
|
LEVEL_5(5, R.style.LevelFive, 25, 60, 89),
|
||||||
|
LEVEL_6(6,R.style.LevelOne,30,70, 90),
|
||||||
|
LEVEL_7(7, R.style.LevelTwo, 40, 80, 90),
|
||||||
|
LEVEL_8(8, R.style.LevelThree, 45, 90, 90),
|
||||||
|
LEVEL_9(9, R.style.LevelFour, 50, 100, 90),
|
||||||
|
LEVEL_10(10, R.style.LevelFive, 55, 110, 90),
|
||||||
|
LEVEL_11(11,R.style.LevelOne, 60, 120, 90),
|
||||||
|
LEVEL_12(12,R.style.LevelTwo,65 , 130, 90),
|
||||||
|
LEVEL_13(13,R.style.LevelThree, 70, 140, 90),
|
||||||
|
LEVEL_14(14,R.style.LevelFour, 75 , 150, 90),
|
||||||
|
LEVEL_15(15,R.style.LevelFive, 80, 160, 90);
|
||||||
|
|
||||||
|
private int levelNumber;
|
||||||
|
private int levelStyle;
|
||||||
|
private int maxUniqueImages;
|
||||||
|
private int maxUploadCount;
|
||||||
|
private int minNonRevertPercentage;
|
||||||
|
|
||||||
|
LevelInfo(int levelNumber,
|
||||||
|
int levelStyle,
|
||||||
|
int maxUniqueImages,
|
||||||
|
int maxUploadCount,
|
||||||
|
int minNonRevertPercentage) {
|
||||||
|
this.levelNumber = levelNumber;
|
||||||
|
this.levelStyle = levelStyle;
|
||||||
|
this.maxUniqueImages = maxUniqueImages;
|
||||||
|
this.maxUploadCount = maxUploadCount;
|
||||||
|
this.minNonRevertPercentage = minNonRevertPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LevelInfo from(int imagesUploaded,
|
||||||
|
int uniqueImagesUsed,
|
||||||
|
int nonRevertRate) {
|
||||||
|
LevelInfo level = LEVEL_15;
|
||||||
|
|
||||||
|
for (LevelInfo levelInfo : LevelInfo.values()) {
|
||||||
|
if (imagesUploaded < levelInfo.maxUploadCount
|
||||||
|
|| uniqueImagesUsed < levelInfo.maxUniqueImages
|
||||||
|
|| nonRevertRate < levelInfo.minNonRevertPercentage ) {
|
||||||
|
level = levelInfo;
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLevelStyle() {
|
||||||
|
return levelStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLevelNumber() {
|
||||||
|
return levelNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxUniqueImages() {
|
||||||
|
return maxUniqueImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxUploadCount() {
|
||||||
|
return maxUploadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinNonRevertPercentage(){
|
||||||
|
return minNonRevertPercentage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,13 @@ import android.os.Bundle;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_COOKIE;
|
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_COOKIE;
|
||||||
|
|
||||||
|
|
@ -34,6 +39,8 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
authCookie = savedInstanceState.getString(AUTH_COOKIE);
|
authCookie = savedInstanceState.getString(AUTH_COOKIE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showBlockStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -45,4 +52,20 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
|
||||||
protected abstract void onAuthCookieAcquired(String authCookie);
|
protected abstract void onAuthCookieAcquired(String authCookie);
|
||||||
|
|
||||||
protected abstract void onAuthFailure();
|
protected abstract void onAuthFailure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes API call to check if user is blocked from Commons. If the user is blocked, a snackbar
|
||||||
|
* is created to notify the user
|
||||||
|
*/
|
||||||
|
protected void showBlockStatus()
|
||||||
|
{
|
||||||
|
Observable.fromCallable(() -> mediaWikiApi.isUserBlockedFromCommons())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.filter(result -> result)
|
||||||
|
.subscribe(result -> {
|
||||||
|
ViewUtil.showSnackbar(findViewById(android.R.id.content), R.string.block_notification);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import android.accounts.Account;
|
||||||
import android.accounts.AccountAuthenticatorActivity;
|
import android.accounts.AccountAuthenticatorActivity;
|
||||||
import android.accounts.AccountAuthenticatorResponse;
|
import android.accounts.AccountAuthenticatorResponse;
|
||||||
import android.accounts.AccountManager;
|
import android.accounts.AccountManager;
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
@ -24,7 +23,6 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
@ -137,11 +135,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startYourself(Context context) {
|
|
||||||
Intent intent = new Intent(context, LoginActivity.class);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void forgotPassword() {
|
private void forgotPassword() {
|
||||||
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
|
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
|
||||||
}
|
}
|
||||||
|
|
@ -168,6 +161,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
if (sessionManager.getCurrentAccount() != null
|
if (sessionManager.getCurrentAccount() != null
|
||||||
&& sessionManager.isUserLoggedIn()
|
&& sessionManager.isUserLoggedIn()
|
||||||
&& sessionManager.getCachedAuthCookie() != null) {
|
&& sessionManager.getCachedAuthCookie() != null) {
|
||||||
|
sessionManager.revalidateAuthToken();
|
||||||
startMainActivity();
|
startMainActivity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -446,4 +440,9 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
loginButton.setEnabled(enabled);
|
loginButton.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void startYourself(Context context) {
|
||||||
|
Intent intent = new Intent(context, LoginActivity.class);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,253 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.DataSetObserver;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.PageTitle;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.explore.ViewPagerAdapter;
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
|
||||||
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity displays details of a particular category
|
||||||
|
* Its generic and simply takes the name of category name in its start intent to load all images, subcategories in
|
||||||
|
* a particular category on wikimedia commons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class CategoryDetailsActivity extends NavigationBaseActivity
|
||||||
|
implements MediaDetailPagerFragment.MediaDetailProvider,
|
||||||
|
AdapterView.OnItemClickListener{
|
||||||
|
|
||||||
|
|
||||||
|
private FragmentManager supportFragmentManager;
|
||||||
|
private CategoryImagesListFragment categoryImagesListFragment;
|
||||||
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
|
private String categoryName;
|
||||||
|
@BindView(R.id.mediaContainer) FrameLayout mediaContainer;
|
||||||
|
@BindView(R.id.tabLayout) TabLayout tabLayout;
|
||||||
|
@BindView(R.id.viewPager) ViewPager viewPager;
|
||||||
|
|
||||||
|
ViewPagerAdapter viewPagerAdapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_category_details);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
supportFragmentManager = getSupportFragmentManager();
|
||||||
|
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(viewPagerAdapter);
|
||||||
|
viewPager.setOffscreenPageLimit(2);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
setTabs();
|
||||||
|
setPageTitle();
|
||||||
|
initDrawer();
|
||||||
|
forceInitBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab,
|
||||||
|
* Set the fragments according to the tab selected in the viewPager.
|
||||||
|
*/
|
||||||
|
private void setTabs() {
|
||||||
|
List<Fragment> fragmentList = new ArrayList<>();
|
||||||
|
List<String> titleList = new ArrayList<>();
|
||||||
|
categoryImagesListFragment = new CategoryImagesListFragment();
|
||||||
|
SubCategoryListFragment subCategoryListFragment = new SubCategoryListFragment();
|
||||||
|
SubCategoryListFragment parentCategoryListFragment = new SubCategoryListFragment();
|
||||||
|
categoryName = getIntent().getStringExtra("categoryName");
|
||||||
|
if (getIntent() != null && categoryName != null) {
|
||||||
|
Bundle arguments = new Bundle();
|
||||||
|
arguments.putString("categoryName", categoryName);
|
||||||
|
arguments.putBoolean("isParentCategory", false);
|
||||||
|
categoryImagesListFragment.setArguments(arguments);
|
||||||
|
subCategoryListFragment.setArguments(arguments);
|
||||||
|
Bundle parentCategoryArguments = new Bundle();
|
||||||
|
parentCategoryArguments.putString("categoryName", categoryName);
|
||||||
|
parentCategoryArguments.putBoolean("isParentCategory", true);
|
||||||
|
parentCategoryListFragment.setArguments(parentCategoryArguments);
|
||||||
|
}
|
||||||
|
fragmentList.add(categoryImagesListFragment);
|
||||||
|
titleList.add("MEDIA");
|
||||||
|
fragmentList.add(subCategoryListFragment);
|
||||||
|
titleList.add("SUBCATEGORIES");
|
||||||
|
fragmentList.add(parentCategoryListFragment);
|
||||||
|
titleList.add("PARENT CATEGORIES");
|
||||||
|
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||||
|
viewPagerAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the passed categoryName from the intents and displays it as the page title
|
||||||
|
*/
|
||||||
|
private void setPageTitle() {
|
||||||
|
if (getIntent() != null && getIntent().getStringExtra("categoryName") != null) {
|
||||||
|
setTitle(getIntent().getStringExtra("categoryName"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called onClick of media inside category details (CategoryImageListFragment).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||||
|
tabLayout.setVisibility(View.GONE);
|
||||||
|
viewPager.setVisibility(View.GONE);
|
||||||
|
mediaContainer.setVisibility(View.VISIBLE);
|
||||||
|
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||||
|
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||||
|
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||||
|
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.mediaContainer, mediaDetails)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
supportFragmentManager.executePendingTransactions();
|
||||||
|
}
|
||||||
|
mediaDetails.showImage(i);
|
||||||
|
forceInitBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumers should be simply using this method to use this activity.
|
||||||
|
* @param context A Context of the application package implementing this class.
|
||||||
|
* @param categoryName Name of the category for displaying its details
|
||||||
|
*/
|
||||||
|
public static void startYourself(Context context, String categoryName) {
|
||||||
|
Intent intent = new Intent(context, CategoryDetailsActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||||
|
intent.putExtra("categoryName", categoryName);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||||
|
* @param i It is the index of which media object is to be returned which is same as
|
||||||
|
* current index of viewPager.
|
||||||
|
* @return Media Object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Media getMediaAtPosition(int i) {
|
||||||
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
// not yet ready to return data
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (Media) categoryImagesListFragment.getAdapter().getItem(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @return Total Media count in the adapter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return categoryImagesListFragment.getAdapter().getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void notifyDatasetChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void registerDataSetObserver(DataSetObserver observer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method inflates the menu in the toolbar
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.fragment_category_detail, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method handles the logic on ItemSelect in toolbar menu
|
||||||
|
* Currently only 1 choice is available to open category details page in browser
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
|
// Handle item selection
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_browser_current_category:
|
||||||
|
Intent viewIntent = new Intent();
|
||||||
|
viewIntent.setAction(Intent.ACTION_VIEW);
|
||||||
|
viewIntent.setData(new PageTitle(categoryName).getCanonicalUri());
|
||||||
|
//check if web browser available
|
||||||
|
if (viewIntent.resolveActivity(this.getPackageManager()) != null) {
|
||||||
|
startActivity(viewIntent);
|
||||||
|
} else {
|
||||||
|
Toast toast = Toast.makeText(this, getString(R.string.no_web_browser), LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on backPressed of anyFragment in the activity.
|
||||||
|
* If condition is called when mediaDetailFragment is opened.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (supportFragmentManager.getBackStackEntryCount() == 1){
|
||||||
|
// back to search so show search toolbar and hide navigation toolbar
|
||||||
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
viewPager.setVisibility(View.VISIBLE);
|
||||||
|
mediaContainer.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import org.w3c.dom.NodeList;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -27,12 +28,30 @@ public class CategoryImageUtils {
|
||||||
List<Media> categoryImages = new ArrayList<>();
|
List<Media> categoryImages = new ArrayList<>();
|
||||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||||
Node node = childNodes.item(i);
|
Node node = childNodes.item(i);
|
||||||
categoryImages.add(getMediaFromPage(node));
|
if (getMediaFromPage(node).getFilename().substring(0,5).equals("File:")){
|
||||||
|
categoryImages.add(getMediaFromPage(node));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return categoryImages;
|
return categoryImages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method iterates over the child nodes to return a list of Subcategory name
|
||||||
|
* sorted alphabetically
|
||||||
|
* @param childNodes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<String> getSubCategoryList(NodeList childNodes) {
|
||||||
|
List<String> subCategories = new ArrayList<>();
|
||||||
|
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||||
|
Node node = childNodes.item(i);
|
||||||
|
subCategories.add(getMediaFromPage(node).getFilename());
|
||||||
|
}
|
||||||
|
Collections.sort(subCategories);
|
||||||
|
return subCategories;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Media object from the XML response as received by the API
|
* Creates a new Media object from the XML response as received by the API
|
||||||
* @param node
|
* @param node
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ import android.database.DataSetObserver;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
|
@ -13,8 +16,9 @@ import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
|
import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import timber.log.Timber;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This activity displays pictures of a particular category
|
* This activity displays pictures of a particular category
|
||||||
|
|
@ -44,6 +48,16 @@ public class CategoryImagesActivity
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on backPressed of anyFragment in the activity.
|
||||||
|
* We are changing the icon here from back to hamburger icon.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
initDrawer();
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
@ -55,11 +69,6 @@ public class CategoryImagesActivity
|
||||||
supportFragmentManager = getSupportFragmentManager();
|
supportFragmentManager = getSupportFragmentManager();
|
||||||
setCategoryImagesFragment();
|
setCategoryImagesFragment();
|
||||||
supportFragmentManager.addOnBackStackChangedListener(this);
|
supportFragmentManager.addOnBackStackChangedListener(this);
|
||||||
if (savedInstanceState != null) {
|
|
||||||
mediaDetails = (MediaDetailPagerFragment) supportFragmentManager
|
|
||||||
.findFragmentById(R.id.fragmentContainer);
|
|
||||||
|
|
||||||
}
|
|
||||||
requestAuthToken();
|
requestAuthToken();
|
||||||
initDrawer();
|
initDrawer();
|
||||||
setPageTitle();
|
setPageTitle();
|
||||||
|
|
@ -95,6 +104,9 @@ public class CategoryImagesActivity
|
||||||
public void onBackStackChanged() {
|
public void onBackStackChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called onClick of media inside category details (CategoryImageListFragment).
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||||
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||||
|
|
@ -103,17 +115,34 @@ public class CategoryImagesActivity
|
||||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(R.id.fragmentContainer, mediaDetails)
|
.hide(supportFragmentManager.getFragments().get(supportFragmentManager.getBackStackEntryCount()))
|
||||||
|
.add(R.id.fragmentContainer, mediaDetails)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
supportFragmentManager.executePendingTransactions();
|
// Reason for using hide, add instead of replace is to maintain scroll position after
|
||||||
|
// coming back to the search activity. See https://github.com/commons-app/apps-android-commons/issues/1631
|
||||||
|
// https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550 supportFragmentManager.executePendingTransactions();
|
||||||
}
|
}
|
||||||
mediaDetails.showImage(i);
|
mediaDetails.showImage(i);
|
||||||
|
forceInitBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on backPressed when mediaDetailFragment is opened in the activity.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
if (supportFragmentManager.getBackStackEntryCount()==1){
|
||||||
|
//FIXME: Temporary fix for screen rotation inside media details. If we don't call onBackPressed then fragment stack is increasing every time.
|
||||||
|
//FIXME: Similar issue like this https://github.com/commons-app/apps-android-commons/issues/894
|
||||||
|
onBackPressed();
|
||||||
|
}
|
||||||
|
super.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumers should be simply using this method to use this activity.
|
* Consumers should be simply using this method to use this activity.
|
||||||
* @param context
|
* @param context A Context of the application package implementing this class.
|
||||||
* @param title Page title
|
* @param title Page title
|
||||||
* @param categoryName Name of the category for displaying its images
|
* @param categoryName Name of the category for displaying its images
|
||||||
*/
|
*/
|
||||||
|
|
@ -125,6 +154,12 @@ public class CategoryImagesActivity
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||||
|
* @param i It is the index of which media object is to be returned which is same as
|
||||||
|
* current index of viewPager.
|
||||||
|
* @return Media Object
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Media getMediaAtPosition(int i) {
|
public Media getMediaAtPosition(int i) {
|
||||||
if (categoryImagesListFragment.getAdapter() == null) {
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
|
@ -135,6 +170,11 @@ public class CategoryImagesActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @return Total Media count in the adapter
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getTotalMediaCount() {
|
public int getTotalMediaCount() {
|
||||||
if (categoryImagesListFragment.getAdapter() == null) {
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
|
@ -143,18 +183,57 @@ public class CategoryImagesActivity
|
||||||
return categoryImagesListFragment.getAdapter().getCount();
|
return categoryImagesListFragment.getAdapter().getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void notifyDatasetChanged() {
|
public void notifyDatasetChanged() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void registerDataSetObserver(DataSetObserver observer) {
|
public void registerDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method inflates the menu in the toolbar
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.menu_search, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method handles the logic on ItemSelect in toolbar menu
|
||||||
|
* Currently only 1 choice is available to open search page of the app
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
|
// Handle item selection
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_search:
|
||||||
|
NavigationBaseActivity.startActivityWithFlags(this, SearchActivity.class);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ import android.widget.AdapterView;
|
||||||
import android.widget.GridView;
|
import android.widget.GridView;
|
||||||
import android.widget.ListAdapter;
|
import android.widget.ListAdapter;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
@ -48,9 +50,9 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
TextView statusTextView;
|
TextView statusTextView;
|
||||||
@BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar;
|
@BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar;
|
||||||
@BindView(R.id.categoryImagesList) GridView gridView;
|
@BindView(R.id.categoryImagesList) GridView gridView;
|
||||||
|
@BindView(R.id.parentLayout) RelativeLayout parentLayout;
|
||||||
private boolean hasMoreImages = true;
|
private boolean hasMoreImages = true;
|
||||||
private boolean isLoading;
|
private boolean isLoading = true;
|
||||||
private String categoryName = null;
|
private String categoryName = null;
|
||||||
|
|
||||||
@Inject CategoryImageController controller;
|
@Inject CategoryImageController controller;
|
||||||
|
|
@ -123,7 +125,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
statusTextView.setVisibility(VISIBLE);
|
statusTextView.setVisibility(VISIBLE);
|
||||||
statusTextView.setText(getString(R.string.no_internet));
|
statusTextView.setText(getString(R.string.no_internet));
|
||||||
} else {
|
} else {
|
||||||
ViewUtil.showSnackbar(gridView, R.string.no_internet);
|
ViewUtil.showSnackbar(parentLayout, R.string.no_internet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,15 +134,20 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
* @param throwable
|
* @param throwable
|
||||||
*/
|
*/
|
||||||
private void handleError(Throwable throwable) {
|
private void handleError(Throwable throwable) {
|
||||||
Timber.e(throwable, "Error occurred while loading featured images");
|
Timber.e(throwable, "Error occurred while loading images inside a category");
|
||||||
initErrorView();
|
try{
|
||||||
|
ViewUtil.showSnackbar(parentLayout, R.string.error_loading_images);
|
||||||
|
initErrorView();
|
||||||
|
}catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the UI updates for a error scenario
|
* Handles the UI updates for a error scenario
|
||||||
*/
|
*/
|
||||||
private void initErrorView() {
|
private void initErrorView() {
|
||||||
ViewUtil.showSnackbar(gridView, R.string.error_loading_images);
|
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||||
statusTextView.setVisibility(VISIBLE);
|
statusTextView.setVisibility(VISIBLE);
|
||||||
|
|
@ -152,7 +159,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the adapter with a list of Media objects
|
* Initializes the adapter with a list of Media objects
|
||||||
* @param mediaList
|
* @param mediaList List of new Media to be displayed
|
||||||
*/
|
*/
|
||||||
private void setAdapter(List<Media> mediaList) {
|
private void setAdapter(List<Media> mediaList) {
|
||||||
gridAdapter = new GridViewAdapter(this.getContext(), R.layout.layout_category_images, mediaList);
|
gridAdapter = new GridViewAdapter(this.getContext(), R.layout.layout_category_images, mediaList);
|
||||||
|
|
@ -176,6 +183,9 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
fetchMoreImages();
|
fetchMoreImages();
|
||||||
}
|
}
|
||||||
|
if (!hasMoreImages){
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +211,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
/**
|
/**
|
||||||
* Handles the success scenario
|
* Handles the success scenario
|
||||||
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||||
* @param collection
|
* @param collection List of new Media to be displayed
|
||||||
*/
|
*/
|
||||||
private void handleSuccess(List<Media> collection) {
|
private void handleSuccess(List<Media> collection) {
|
||||||
if(collection == null || collection.isEmpty()) {
|
if(collection == null || collection.isEmpty()) {
|
||||||
|
|
@ -213,6 +223,10 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
if(gridAdapter == null) {
|
if(gridAdapter == null) {
|
||||||
setAdapter(collection);
|
setAdapter(collection);
|
||||||
} else {
|
} else {
|
||||||
|
if (gridAdapter.containsAll(collection)) {
|
||||||
|
hasMoreImages = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
gridAdapter.addItems(collection);
|
gridAdapter.addItems(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,17 +235,13 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
statusTextView.setVisibility(GONE);
|
statusTextView.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It return an instance of gridView adapter which helps in extracting media details
|
||||||
|
* used by the gridView
|
||||||
|
* @return GridView Adapter
|
||||||
|
*/
|
||||||
public ListAdapter getAdapter() {
|
public ListAdapter getAdapter() {
|
||||||
return gridView.getAdapter();
|
return gridView.getAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will be called on back pressed of CategoryImagesActivity.
|
|
||||||
* It initializes the grid view by setting adapter.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
gridView.setAdapter(gridAdapter);
|
|
||||||
super.onResume();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,18 @@ public class GridViewAdapter extends ArrayAdapter {
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the first item in the new list with old list and returns true if they are same
|
||||||
|
* Its triggered on successful response of the fetch images API.
|
||||||
|
* @param images
|
||||||
|
*/
|
||||||
|
public boolean containsAll(List<Media> images){
|
||||||
|
if (data == null) {
|
||||||
|
data = new ArrayList<>();
|
||||||
|
}
|
||||||
|
return images.get(0).getFilename().equals(data.get(0).getFilename());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return data == null || data.isEmpty();
|
return data == null || data.isEmpty();
|
||||||
|
|
@ -66,7 +78,7 @@ public class GridViewAdapter extends ArrayAdapter {
|
||||||
MediaWikiImageView imageView = convertView.findViewById(R.id.categoryImageView);
|
MediaWikiImageView imageView = convertView.findViewById(R.id.categoryImageView);
|
||||||
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
||||||
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
||||||
fileName.setText(item.getFilename());
|
fileName.setText(item.getDisplayTitle());
|
||||||
setAuthorView(item, author);
|
setAuthorView(item, author);
|
||||||
imageView.setMedia(item);
|
imageView.setMedia(item);
|
||||||
return convertView;
|
return convertView;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
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.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
import fr.free.nrw.commons.explore.categories.SearchCategoriesAdapterFactory;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the category search screen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
|
||||||
|
@BindView(R.id.imagesListBox)
|
||||||
|
RecyclerView categoriesRecyclerView;
|
||||||
|
@BindView(R.id.imageSearchInProgress)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
@BindView(R.id.imagesNotFound)
|
||||||
|
TextView categoriesNotFoundView;
|
||||||
|
|
||||||
|
private String categoryName = null;
|
||||||
|
@Inject MediaWikiApi mwApi;
|
||||||
|
|
||||||
|
private RVRendererAdapter<String> categoriesAdapter;
|
||||||
|
private boolean isParentCategory = true;
|
||||||
|
|
||||||
|
private final SearchCategoriesAdapterFactory adapterFactory = new SearchCategoriesAdapterFactory(item -> {
|
||||||
|
// Open SubCategory Details page
|
||||||
|
Intent intent = new Intent(getContext(), CategoryDetailsActivity.class);
|
||||||
|
intent.putExtra("categoryName", item);
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
categoryName = getArguments().getString("categoryName");
|
||||||
|
isParentCategory = getArguments().getBoolean("isParentCategory");
|
||||||
|
initSubCategoryList();
|
||||||
|
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||||
|
categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
categoriesRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||||
|
}
|
||||||
|
ArrayList<String> items = new ArrayList<>();
|
||||||
|
categoriesAdapter = adapterFactory.create(items);
|
||||||
|
categoriesRecyclerView.setAdapter(categoriesAdapter);
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for internet connection and then initializes the recycler view with 25 categories of the searched query
|
||||||
|
* Clearing categoryAdapter every time new keyword is searched so that user can see only new results
|
||||||
|
*/
|
||||||
|
public void initSubCategoryList() {
|
||||||
|
categoriesNotFoundView.setVisibility(GONE);
|
||||||
|
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
if (!isParentCategory){
|
||||||
|
Observable.fromCallable(() -> mwApi.getSubCategoryList(categoryName))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}else {
|
||||||
|
Observable.fromCallable(() -> mwApi.getParentCategoryList(categoryName))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
* @param subCategoryList
|
||||||
|
*/
|
||||||
|
private void handleSuccess(List<String> subCategoryList) {
|
||||||
|
if(subCategoryList == null || subCategoryList.isEmpty()) {
|
||||||
|
initEmptyView();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
categoriesAdapter.addAll(subCategoryList);
|
||||||
|
categoriesAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
* @param throwable
|
||||||
|
*/
|
||||||
|
private void handleError(Throwable throwable) {
|
||||||
|
if (!isParentCategory){
|
||||||
|
Timber.e(throwable, "Error occurred while loading queried subcategories");
|
||||||
|
ViewUtil.showSnackbar(categoriesRecyclerView,R.string.error_loading_categories);
|
||||||
|
}else {
|
||||||
|
Timber.e(throwable, "Error occurred while loading queried parentcategories");
|
||||||
|
ViewUtil.showSnackbar(categoriesRecyclerView,R.string.error_loading_categories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a empty results scenario
|
||||||
|
*/
|
||||||
|
private void initEmptyView() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
categoriesNotFoundView.setVisibility(VISIBLE);
|
||||||
|
if (!isParentCategory){
|
||||||
|
categoriesNotFoundView.setText(getString(R.string.no_subcategory_found));
|
||||||
|
}else {
|
||||||
|
categoriesNotFoundView.setText(getString(R.string.no_parentcategory_found));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
private void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
ViewUtil.showSnackbar(categoriesRecyclerView, R.string.no_internet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ import android.net.Uri;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
@ -34,8 +35,10 @@ import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.quiz.QuizChecker;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.upload.UploadService;
|
import fr.free.nrw.commons.upload.UploadService;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
@ -137,13 +140,21 @@ public class ContributionsActivity
|
||||||
|
|
||||||
getSupportLoaderManager().initLoader(0, null, this);
|
getSupportLoaderManager().initLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAuthToken();
|
requestAuthToken();
|
||||||
initDrawer();
|
initDrawer();
|
||||||
setTitle(getString(R.string.title_activity_contributions));
|
setTitle(getString(R.string.title_activity_contributions));
|
||||||
|
|
||||||
|
|
||||||
|
if(checkAccount()) {
|
||||||
|
new QuizChecker(this,
|
||||||
|
sessionManager.getCurrentAccount().name,
|
||||||
|
mediaWikiApi);
|
||||||
|
}
|
||||||
if(!BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
|
if(!BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
|
||||||
setUploadCount();
|
setUploadCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -337,6 +348,21 @@ public class ContributionsActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to ensure user is logged in
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean checkAccount() {
|
||||||
|
Account currentAccount = sessionManager.getCurrentAccount();
|
||||||
|
if (currentAccount == null) {
|
||||||
|
Timber.d("Current account is null");
|
||||||
|
ViewUtil.showLongToast(this, getResources().getString(R.string.user_not_logged_in));
|
||||||
|
sessionManager.forceLogin(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackStackChanged() {
|
public void onBackStackChanged() {
|
||||||
initBackButton();
|
initBackButton();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
|
@ -48,8 +47,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||||
TextView waitingMessage;
|
TextView waitingMessage;
|
||||||
@BindView(R.id.loadingContributionsProgressBar)
|
@BindView(R.id.loadingContributionsProgressBar)
|
||||||
ProgressBar progressBar;
|
ProgressBar progressBar;
|
||||||
@BindView(R.id.swipeRefreshLayout)
|
|
||||||
SwipeRefreshLayout swipeRefreshLayout;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Named("prefs")
|
@Named("prefs")
|
||||||
|
|
@ -67,13 +64,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||||
ButterKnife.bind(this, v);
|
ButterKnife.bind(this, v);
|
||||||
|
|
||||||
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
||||||
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
|
|
||||||
@Override
|
|
||||||
public void onRefresh() {
|
|
||||||
((ContributionsListAdapter)contributionsList.getAdapter()).notifyDataSetChanged();
|
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
|
Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
|
||||||
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
|
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
import fr.free.nrw.commons.category.CategoryDao;
|
import fr.free.nrw.commons.category.CategoryDao;
|
||||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||||
|
|
||||||
public class DBOpenHelper extends SQLiteOpenHelper {
|
public class DBOpenHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "commons.db";
|
private static final String DATABASE_NAME = "commons.db";
|
||||||
private static final int DATABASE_VERSION = 6;
|
private static final int DATABASE_VERSION = 7;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not use directly - @Inject an instance where it's needed and let
|
* Do not use directly - @Inject an instance where it's needed and let
|
||||||
|
|
@ -26,6 +27,7 @@ public class DBOpenHelper extends SQLiteOpenHelper {
|
||||||
ContributionDao.Table.onCreate(sqLiteDatabase);
|
ContributionDao.Table.onCreate(sqLiteDatabase);
|
||||||
ModifierSequenceDao.Table.onCreate(sqLiteDatabase);
|
ModifierSequenceDao.Table.onCreate(sqLiteDatabase);
|
||||||
CategoryDao.Table.onCreate(sqLiteDatabase);
|
CategoryDao.Table.onCreate(sqLiteDatabase);
|
||||||
|
RecentSearchesDao.Table.onCreate(sqLiteDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -33,5 +35,6 @@ public class DBOpenHelper extends SQLiteOpenHelper {
|
||||||
ContributionDao.Table.onUpdate(sqLiteDatabase, from, to);
|
ContributionDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
ModifierSequenceDao.Table.onUpdate(sqLiteDatabase, from, to);
|
ModifierSequenceDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
|
CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
|
RecentSearchesDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,14 @@ import dagger.Module;
|
||||||
import dagger.android.ContributesAndroidInjector;
|
import dagger.android.ContributesAndroidInjector;
|
||||||
import fr.free.nrw.commons.AboutActivity;
|
import fr.free.nrw.commons.AboutActivity;
|
||||||
import fr.free.nrw.commons.WelcomeActivity;
|
import fr.free.nrw.commons.WelcomeActivity;
|
||||||
|
import fr.free.nrw.commons.achievements.AchievementsActivity;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.auth.SignupActivity;
|
import fr.free.nrw.commons.auth.SignupActivity;
|
||||||
|
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||||
|
import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
|
|
||||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||||
|
|
@ -50,4 +54,14 @@ public abstract class ActivityBuilderModule {
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract CategoryImagesActivity bindFeaturedImagesActivity();
|
abstract CategoryImagesActivity bindFeaturedImagesActivity();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract SearchActivity bindSearchActivity();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract CategoryDetailsActivity bindCategoryDetailsActivity();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract AchievementsActivity bindAchievementsActivity();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,16 @@ import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.util.LruCache;
|
import android.support.v4.util.LruCache;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
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.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||||
import fr.free.nrw.commons.upload.UploadController;
|
import fr.free.nrw.commons.upload.UploadController;
|
||||||
|
|
@ -28,6 +24,7 @@ import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
|
||||||
|
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
|
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
|
||||||
|
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.RECENT_SEARCH_AUTHORITY;
|
||||||
import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY;
|
import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|
@ -57,6 +54,18 @@ public class CommonsApplicationModule {
|
||||||
return context.getContentResolver().acquireContentProviderClient(CATEGORY_AUTHORITY);
|
return context.getContentResolver().acquireContentProviderClient(CATEGORY_AUTHORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to provide instance of RecentSearchContentProviderClient
|
||||||
|
* which provides content of Recent Searches from database
|
||||||
|
* @param context
|
||||||
|
* @return returns RecentSearchContentProviderClient
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Named("recentsearch")
|
||||||
|
public ContentProviderClient provideRecentSearchContentProviderClient(Context context) {
|
||||||
|
return context.getContentResolver().acquireContentProviderClient(RECENT_SEARCH_AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("contribution")
|
@Named("contribution")
|
||||||
public ContentProviderClient provideContributionContentProviderClient(Context context) {
|
public ContentProviderClient provideContributionContentProviderClient(Context context) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import dagger.Module;
|
||||||
import dagger.android.ContributesAndroidInjector;
|
import dagger.android.ContributesAndroidInjector;
|
||||||
import fr.free.nrw.commons.category.CategoryContentProvider;
|
import fr.free.nrw.commons.category.CategoryContentProvider;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|
@ -19,4 +20,7 @@ public abstract class ContentProviderBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract CategoryContentProvider bindCategoryContentProvider();
|
abstract CategoryContentProvider bindCategoryContentProvider();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract RecentSearchesContentProvider bindRecentSearchesContentProvider();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,12 @@ package fr.free.nrw.commons.di;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.android.ContributesAndroidInjector;
|
import dagger.android.ContributesAndroidInjector;
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
|
import fr.free.nrw.commons.category.SubCategoryListFragment;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
||||||
import fr.free.nrw.commons.category.CategoryImagesListFragment;
|
import fr.free.nrw.commons.category.CategoryImagesListFragment;
|
||||||
|
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||||
|
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
||||||
|
|
@ -51,4 +55,16 @@ public abstract class FragmentBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
|
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract SubCategoryListFragment bindSubCategoryListFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract SearchImageFragment bindBrowseImagesListFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract SearchCategoryFragment bindSearchCategoryListFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract RecentSearchesFragment bindRecentSearchesFragment();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
package fr.free.nrw.commons.explore;
|
||||||
|
|
||||||
|
import android.database.DataSetObserver;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.SearchView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
|
import com.jakewharton.rxbinding2.widget.RxSearchView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||||
|
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents search screen of this app
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SearchActivity extends NavigationBaseActivity implements MediaDetailPagerFragment.MediaDetailProvider{
|
||||||
|
|
||||||
|
@BindView(R.id.toolbar_search) Toolbar toolbar;
|
||||||
|
@BindView(R.id.searchHistoryContainer) FrameLayout searchHistoryContainer;
|
||||||
|
@BindView(R.id.mediaContainer) FrameLayout mediaContainer;
|
||||||
|
@BindView(R.id.searchBox) SearchView searchView;
|
||||||
|
@BindView(R.id.tabLayout) TabLayout tabLayout;
|
||||||
|
@BindView(R.id.viewPager) ViewPager viewPager;
|
||||||
|
|
||||||
|
private SearchImageFragment searchImageFragment;
|
||||||
|
private SearchCategoryFragment searchCategoryFragment;
|
||||||
|
private RecentSearchesFragment recentSearchesFragment;
|
||||||
|
private FragmentManager supportFragmentManager;
|
||||||
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
|
ViewPagerAdapter viewPagerAdapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_search);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
initDrawer();
|
||||||
|
setTitle(getString(R.string.title_activity_search));
|
||||||
|
toolbar.setNavigationOnClickListener(v->onBackPressed());
|
||||||
|
supportFragmentManager = getSupportFragmentManager();
|
||||||
|
setSearchHistoryFragment();
|
||||||
|
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(viewPagerAdapter);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
setTabs();
|
||||||
|
searchView.setQueryHint(getString(R.string.search_commons));
|
||||||
|
searchView.onActionViewExpanded();
|
||||||
|
searchView.clearFocus();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets the search history fragment.
|
||||||
|
* Search history fragment is displayed when query is empty.
|
||||||
|
*/
|
||||||
|
private void setSearchHistoryFragment() {
|
||||||
|
recentSearchesFragment = new RecentSearchesFragment();
|
||||||
|
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
|
||||||
|
transaction.add(R.id.searchHistoryContainer, recentSearchesFragment).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the titles in the tabLayout and fragments in the viewPager
|
||||||
|
*/
|
||||||
|
public void setTabs() {
|
||||||
|
List<Fragment> fragmentList = new ArrayList<>();
|
||||||
|
List<String> titleList = new ArrayList<>();
|
||||||
|
searchImageFragment = new SearchImageFragment();
|
||||||
|
searchCategoryFragment= new SearchCategoryFragment();
|
||||||
|
fragmentList.add(searchImageFragment);
|
||||||
|
titleList.add("MEDIA");
|
||||||
|
fragmentList.add(searchCategoryFragment);
|
||||||
|
titleList.add("CATEGORIES");
|
||||||
|
|
||||||
|
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||||
|
viewPagerAdapter.notifyDataSetChanged();
|
||||||
|
RxSearchView.queryTextChanges(searchView)
|
||||||
|
.takeUntil(RxView.detaches(searchView))
|
||||||
|
.debounce(500, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe( query -> {
|
||||||
|
//update image list
|
||||||
|
if (!TextUtils.isEmpty(query)) {
|
||||||
|
viewPager.setVisibility(View.VISIBLE);
|
||||||
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
searchHistoryContainer.setVisibility(View.GONE);
|
||||||
|
searchImageFragment.updateImageList(query.toString());
|
||||||
|
searchCategoryFragment.updateCategoryList(query.toString());
|
||||||
|
}else {
|
||||||
|
viewPager.setVisibility(View.GONE);
|
||||||
|
tabLayout.setVisibility(View.GONE);
|
||||||
|
searchHistoryContainer.setVisibility(View.VISIBLE);
|
||||||
|
recentSearchesFragment.updateRecentSearches();
|
||||||
|
// open search history fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns Media Object at position
|
||||||
|
* @param i position of Media in the imagesRecyclerView adapter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Media getMediaAtPosition(int i) {
|
||||||
|
return searchImageFragment.getImageAtPosition(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns total number of images present in the imagesRecyclerView adapter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
return searchImageFragment.getTotalImagesCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void notifyDatasetChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void registerDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is never called but it was in MediaDetailProvider Interface
|
||||||
|
* so it needs to be overrided.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open media detail pager fragment on click of image in search results
|
||||||
|
* @param index item index that should be opened
|
||||||
|
*/
|
||||||
|
public void onSearchImageClicked(int index) {
|
||||||
|
ViewUtil.hideKeyboard(this.findViewById(R.id.searchBox));
|
||||||
|
toolbar.setVisibility(View.GONE);
|
||||||
|
tabLayout.setVisibility(View.GONE);
|
||||||
|
viewPager.setVisibility(View.GONE);
|
||||||
|
mediaContainer.setVisibility(View.VISIBLE);
|
||||||
|
setNavigationBaseToolbarVisibility(true);
|
||||||
|
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||||
|
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||||
|
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||||
|
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.hide(supportFragmentManager.getFragments().get(supportFragmentManager.getBackStackEntryCount()))
|
||||||
|
.add(R.id.mediaContainer, mediaDetails)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
// Reason for using hide, add instead of replace is to maintain scroll position after
|
||||||
|
// coming back to the search activity. See https://github.com/commons-app/apps-android-commons/issues/1631
|
||||||
|
// https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550
|
||||||
|
supportFragmentManager.executePendingTransactions();
|
||||||
|
}
|
||||||
|
mediaDetails.showImage(index);
|
||||||
|
forceInitBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on Screen Rotation
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
if (supportFragmentManager.getBackStackEntryCount()==1){
|
||||||
|
//FIXME: Temporary fix for screen rotation inside media details. If we don't call onBackPressed then fragment stack is increasing every time.
|
||||||
|
//FIXME: Similar issue like this https://github.com/commons-app/apps-android-commons/issues/894
|
||||||
|
// This is called on screen rotation when user is inside media details. Ideally it should show Media Details but since we are not saving the state now. We are throwing the user to search screen otherwise the app was crashing.
|
||||||
|
//
|
||||||
|
onBackPressed();
|
||||||
|
}
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on backPressed of anyFragment in the activity.
|
||||||
|
* If condition is called when mediaDetailFragment is opened.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
|
||||||
|
// back to search so show search toolbar and hide navigation toolbar
|
||||||
|
toolbar.setVisibility(View.VISIBLE);
|
||||||
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
viewPager.setVisibility(View.VISIBLE);
|
||||||
|
mediaContainer.setVisibility(View.GONE);
|
||||||
|
setNavigationBaseToolbarVisibility(false);
|
||||||
|
}else {
|
||||||
|
toolbar.setVisibility(View.GONE);
|
||||||
|
setNavigationBaseToolbarVisibility(true);
|
||||||
|
}
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on click of a recent search to update query in SearchView.
|
||||||
|
* @param query Recent Search Query
|
||||||
|
*/
|
||||||
|
public void updateText(String query) {
|
||||||
|
searchView.setQuery(query, true);
|
||||||
|
// Clear focus of searchView now. searchView.clearFocus(); does not seem to work Check the below link for more details.
|
||||||
|
// https://stackoverflow.com/questions/6117967/how-to-remove-focus-without-setting-focus-to-another-control/15481511
|
||||||
|
viewPager.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/src/main/java/fr/free/nrw/commons/explore/ViewPagerAdapter.java
Executable file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package fr.free.nrw.commons.explore;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This adapter will be used to display fragments in a ViewPager
|
||||||
|
*/
|
||||||
|
public class ViewPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
private List<Fragment> fragmentList = new ArrayList<>();
|
||||||
|
private List<String> fragmentTitleList = new ArrayList<>();
|
||||||
|
|
||||||
|
public ViewPagerAdapter(FragmentManager manager) {
|
||||||
|
super(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the fragment of the viewpager at a particular position
|
||||||
|
* @param position
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
return fragmentList.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the total number of fragments in the viewpager.
|
||||||
|
* @return size
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return fragmentList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets the fragment and title list in the viewpager
|
||||||
|
* @param fragmentList List of all fragments to be displayed in the viewpager
|
||||||
|
* @param fragmentTitleList List of all titles of the fragments
|
||||||
|
*/
|
||||||
|
public void setTabData(List<Fragment> fragmentList, List<String> fragmentTitleList) {
|
||||||
|
this.fragmentList = fragmentList;
|
||||||
|
this.fragmentTitleList = fragmentTitleList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the title of the page at a particular position
|
||||||
|
* @param position
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
return fragmentTitleList.get(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package fr.free.nrw.commons.explore.categories;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import com.pedrogomez.renderers.RendererBuilder;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class helps in creating adapter for categoriesRecyclerView in SearchCategoryFragment,
|
||||||
|
* implementing onClicks on categoriesRecyclerView Items
|
||||||
|
**/
|
||||||
|
public class SearchCategoriesAdapterFactory {
|
||||||
|
private final SearchCategoriesRenderer.CategoryClickedListener listener;
|
||||||
|
|
||||||
|
public SearchCategoriesAdapterFactory(SearchCategoriesRenderer.CategoryClickedListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a recyclerViewAdapter for Categories.
|
||||||
|
* @param searchImageItemList List of category name to be displayed
|
||||||
|
* @return categoriesAdapter
|
||||||
|
**/
|
||||||
|
public RVRendererAdapter<String> create(List<String> searchImageItemList) {
|
||||||
|
RendererBuilder<String> builder = new RendererBuilder<String>().bind(String.class, new SearchCategoriesRenderer(listener));
|
||||||
|
ListAdapteeCollection<String> collection = new ListAdapteeCollection<>(
|
||||||
|
searchImageItemList != null ? searchImageItemList : Collections.<String>emptyList());
|
||||||
|
return new RVRendererAdapter<>(builder, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package fr.free.nrw.commons.explore.categories;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* presentation logic of individual category in search is handled here
|
||||||
|
**/
|
||||||
|
class SearchCategoriesRenderer extends Renderer<String> {
|
||||||
|
@BindView(R.id.textView1) TextView tvCategoryName;
|
||||||
|
|
||||||
|
private final CategoryClickedListener listener;
|
||||||
|
|
||||||
|
SearchCategoriesRenderer(CategoryClickedListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
|
||||||
|
return layoutInflater.inflate(R.layout.item_recent_searches, viewGroup, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUpView(View view) {
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void hookListeners(View view) {
|
||||||
|
view.setOnClickListener(v -> {
|
||||||
|
String item = getContent();
|
||||||
|
if (listener != null) {
|
||||||
|
listener.categoryClicked(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
String item = getContent();
|
||||||
|
tvCategoryName.setText(item.replaceFirst("^Category:", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface CategoryClickedListener {
|
||||||
|
void categoryClicked(String item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
package fr.free.nrw.commons.explore.categories;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the category search screen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
|
||||||
|
@BindView(R.id.imagesListBox)
|
||||||
|
RecyclerView categoriesRecyclerView;
|
||||||
|
@BindView(R.id.imageSearchInProgress)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
@BindView(R.id.imagesNotFound)
|
||||||
|
TextView categoriesNotFoundView;
|
||||||
|
String query;
|
||||||
|
|
||||||
|
@Inject RecentSearchesDao recentSearchesDao;
|
||||||
|
@Inject MediaWikiApi mwApi;
|
||||||
|
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||||
|
|
||||||
|
private RVRendererAdapter<String> categoriesAdapter;
|
||||||
|
private List<String> queryList = new ArrayList<>();
|
||||||
|
|
||||||
|
private final SearchCategoriesAdapterFactory adapterFactory = new SearchCategoriesAdapterFactory(item -> {
|
||||||
|
// Called on Click of a individual category Item
|
||||||
|
// Open Category Details activity
|
||||||
|
CategoryDetailsActivity.startYourself(getContext(), item);
|
||||||
|
saveQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method saves Search Query in the Recent Searches Database.
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
|
private void saveQuery(String query) {
|
||||||
|
RecentSearch recentSearch = recentSearchesDao.find(query);
|
||||||
|
|
||||||
|
// Newly searched query...
|
||||||
|
if (recentSearch == null) {
|
||||||
|
recentSearch = new RecentSearch(null, query, new Date());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recentSearch.setLastSearched(new Date());
|
||||||
|
}
|
||||||
|
recentSearchesDao.save(recentSearch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||||
|
categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
categoriesRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||||
|
}
|
||||||
|
ArrayList<String> items = new ArrayList<>();
|
||||||
|
categoriesAdapter = adapterFactory.create(items);
|
||||||
|
categoriesRecyclerView.setAdapter(categoriesAdapter);
|
||||||
|
categoriesRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
// check if end of recycler view is reached, if yes then add more results to existing results
|
||||||
|
if (!recyclerView.canScrollVertically(1)) {
|
||||||
|
addCategoriesToList(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for internet connection and then initializes the recycler view with 25 categories of the searched query
|
||||||
|
* Clearing categoryAdapter every time new keyword is searched so that user can see only new results
|
||||||
|
*/
|
||||||
|
public void updateCategoryList(String query) {
|
||||||
|
this.query = query;
|
||||||
|
categoriesNotFoundView.setVisibility(GONE);
|
||||||
|
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
queryList.clear();
|
||||||
|
categoriesAdapter.clear();
|
||||||
|
Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds more results to existing search results
|
||||||
|
*/
|
||||||
|
public void addCategoriesToList(String query) {
|
||||||
|
this.query = query;
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
Observable.fromCallable(() -> mwApi.searchCategory(query,queryList.size()))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handlePaginationSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
* @param mediaList
|
||||||
|
*/
|
||||||
|
private void handlePaginationSuccess(List<String> mediaList) {
|
||||||
|
queryList.addAll(mediaList);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
categoriesAdapter.addAll(mediaList);
|
||||||
|
categoriesAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
* @param mediaList
|
||||||
|
*/
|
||||||
|
private void handleSuccess(List<String> mediaList) {
|
||||||
|
queryList = mediaList;
|
||||||
|
if(mediaList == null || mediaList.isEmpty()) {
|
||||||
|
initErrorView();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
categoriesAdapter.addAll(mediaList);
|
||||||
|
categoriesAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
// check if user is waiting for 5 seconds if yes then save search query to history.
|
||||||
|
Handler handler = new Handler();
|
||||||
|
handler.postDelayed(() -> saveQuery(query), 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
* @param throwable
|
||||||
|
*/
|
||||||
|
private void handleError(Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Error occurred while loading queried categories");
|
||||||
|
try {
|
||||||
|
initErrorView();
|
||||||
|
ViewUtil.showSnackbar(categoriesRecyclerView, R.string.error_loading_categories);
|
||||||
|
}catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
private void initErrorView() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
categoriesNotFoundView.setVisibility(VISIBLE);
|
||||||
|
categoriesNotFoundView.setText(getString(R.string.categories_not_found, query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
private void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
ViewUtil.showSnackbar(categoriesRecyclerView, R.string.no_internet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
package fr.free.nrw.commons.explore.images;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the image search screen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
|
||||||
|
@BindView(R.id.imagesListBox)
|
||||||
|
RecyclerView imagesRecyclerView;
|
||||||
|
@BindView(R.id.imageSearchInProgress)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
@BindView(R.id.imagesNotFound)
|
||||||
|
TextView imagesNotFoundView;
|
||||||
|
String query;
|
||||||
|
|
||||||
|
@Inject RecentSearchesDao recentSearchesDao;
|
||||||
|
@Inject MediaWikiApi mwApi;
|
||||||
|
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||||
|
|
||||||
|
private RVRendererAdapter<Media> imagesAdapter;
|
||||||
|
private List<Media> queryList = new ArrayList<>();
|
||||||
|
|
||||||
|
private final SearchImagesAdapterFactory adapterFactory = new SearchImagesAdapterFactory(item -> {
|
||||||
|
// Called on Click of a individual media Item
|
||||||
|
int index = queryList.indexOf(item);
|
||||||
|
((SearchActivity)getContext()).onSearchImageClicked(index);
|
||||||
|
saveQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method saves Search Query in the Recent Searches Database.
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
|
private void saveQuery(String query) {
|
||||||
|
RecentSearch recentSearch = recentSearchesDao.find(query);
|
||||||
|
|
||||||
|
// Newly searched query...
|
||||||
|
if (recentSearch == null) {
|
||||||
|
recentSearch = new RecentSearch(null, query, new Date());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recentSearch.setLastSearched(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
recentSearchesDao.save(recentSearch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||||
|
imagesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
imagesRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||||
|
}
|
||||||
|
ArrayList<Media> items = new ArrayList<>();
|
||||||
|
imagesAdapter = adapterFactory.create(items);
|
||||||
|
imagesRecyclerView.setAdapter(imagesAdapter);
|
||||||
|
imagesRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
// check if end of recycler view is reached, if yes then add more results to existing results
|
||||||
|
if (!recyclerView.canScrollVertically(1)) {
|
||||||
|
addImagesToList(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public void updateImageList(String query) {
|
||||||
|
this.query = query;
|
||||||
|
imagesNotFoundView.setVisibility(GONE);
|
||||||
|
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
queryList.clear();
|
||||||
|
imagesAdapter.clear();
|
||||||
|
Observable.fromCallable(() -> mwApi.searchImages(query,queryList.size()))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds more results to existing search results
|
||||||
|
*/
|
||||||
|
public void addImagesToList(String query) {
|
||||||
|
this.query = query;
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
Observable.fromCallable(() -> mwApi.searchImages(query,queryList.size()))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handlePaginationSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
* @param mediaList List of media to be added
|
||||||
|
*/
|
||||||
|
private void handlePaginationSuccess(List<Media> mediaList) {
|
||||||
|
queryList.addAll(mediaList);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
imagesAdapter.addAll(mediaList);
|
||||||
|
imagesAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
* @param mediaList List of media to be shown
|
||||||
|
*/
|
||||||
|
private void handleSuccess(List<Media> mediaList) {
|
||||||
|
queryList = mediaList;
|
||||||
|
if(mediaList == null || mediaList.isEmpty()) {
|
||||||
|
initErrorView();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
imagesAdapter.addAll(mediaList);
|
||||||
|
imagesAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
// check if user is waiting for 5 seconds if yes then save search query to history.
|
||||||
|
Handler handler = new Handler();
|
||||||
|
handler.postDelayed(() -> saveQuery(query), 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
* @param throwable
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
private void initErrorView() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
imagesNotFoundView.setVisibility(VISIBLE);
|
||||||
|
imagesNotFoundView.setText(getString(R.string.images_not_found, query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
private void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
ViewUtil.showSnackbar(imagesRecyclerView, R.string.no_internet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns total number of images present in the recyclerview adapter.
|
||||||
|
*/
|
||||||
|
public int getTotalImagesCount(){
|
||||||
|
if (imagesAdapter == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return imagesAdapter.getItemCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns Media Object at position
|
||||||
|
* @param i position of Media in the recyclerview adapter.
|
||||||
|
*/
|
||||||
|
public Media getImageAtPosition(int i) {
|
||||||
|
if (imagesAdapter.getItem(i).getFilename() == null) {
|
||||||
|
// not yet ready to return data
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Media(imagesAdapter.getItem(i).getFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package fr.free.nrw.commons.explore.images;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import com.pedrogomez.renderers.RendererBuilder;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class helps in creating adapter for imagesRecyclerView in SearchImagesFragment,
|
||||||
|
* implementing onClicks on imagesRecyclerView Items
|
||||||
|
**/
|
||||||
|
class SearchImagesAdapterFactory {
|
||||||
|
private final SearchImagesRenderer.ImageClickedListener listener;
|
||||||
|
|
||||||
|
SearchImagesAdapterFactory(SearchImagesRenderer.ImageClickedListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a recyclerViewAdapter for Media.
|
||||||
|
* @param searchImageItemList List of Media objects to be displayed
|
||||||
|
* @return imagesAdapter
|
||||||
|
**/
|
||||||
|
public RVRendererAdapter<Media> create(List<Media> searchImageItemList) {
|
||||||
|
RendererBuilder<Media> builder = new RendererBuilder<Media>()
|
||||||
|
.bind(Media.class, new SearchImagesRenderer(listener));
|
||||||
|
ListAdapteeCollection<Media> collection = new ListAdapteeCollection<>(
|
||||||
|
searchImageItemList != null ? searchImageItemList : Collections.<Media>emptyList());
|
||||||
|
return new RVRendererAdapter<>(builder, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package fr.free.nrw.commons.explore.images;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* presentation logic of individual image in search is handled here
|
||||||
|
**/
|
||||||
|
class SearchImagesRenderer extends Renderer<Media> {
|
||||||
|
@BindView(R.id.categoryImageTitle) TextView tvImageName;
|
||||||
|
@BindView(R.id.categoryImageAuthor) TextView categoryImageAuthor;
|
||||||
|
@BindView(R.id.categoryImageView)
|
||||||
|
MediaWikiImageView browseImage;
|
||||||
|
|
||||||
|
private final ImageClickedListener listener;
|
||||||
|
|
||||||
|
SearchImagesRenderer(ImageClickedListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
|
||||||
|
return layoutInflater.inflate(R.layout.layout_category_images, viewGroup, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUpView(View view) {
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void hookListeners(View view) {
|
||||||
|
view.setOnClickListener(v -> {
|
||||||
|
Media item = getContent();
|
||||||
|
if (listener != null) {
|
||||||
|
listener.imageClicked(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
Media item = getContent();
|
||||||
|
tvImageName.setText(item.getDisplayTitle());
|
||||||
|
browseImage.setMedia(item);
|
||||||
|
setAuthorView(item, categoryImageAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageClickedListener {
|
||||||
|
void imageClicked(Media item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formats author name as "Uploaded by: authorName" and sets it in textview
|
||||||
|
*/
|
||||||
|
private void setAuthorView(Media item, TextView author) {
|
||||||
|
if (item.getCreator() != null && !item.getCreator().equals("")) {
|
||||||
|
author.setVisibility(View.GONE);
|
||||||
|
String uploadedByTemplate = getContext().getString(R.string.image_uploaded_by);
|
||||||
|
author.setText(String.format(uploadedByTemplate, item.getCreator()));
|
||||||
|
} else {
|
||||||
|
author.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package fr.free.nrw.commons.explore.recentsearches;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a recently searched query
|
||||||
|
* Example - query = "butterfly"
|
||||||
|
*/
|
||||||
|
public class RecentSearch {
|
||||||
|
private Uri contentUri;
|
||||||
|
private String query;
|
||||||
|
private Date lastSearched;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param contentUri the content URI for this query
|
||||||
|
* @param query query name
|
||||||
|
* @param lastSearched last searched date
|
||||||
|
*/
|
||||||
|
public RecentSearch(Uri contentUri, String query, Date lastSearched) {
|
||||||
|
this.contentUri = contentUri;
|
||||||
|
this.query = query;
|
||||||
|
this.lastSearched = lastSearched;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets query name
|
||||||
|
* @return query name
|
||||||
|
*/
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets last searched date
|
||||||
|
* @return Last searched date
|
||||||
|
*/
|
||||||
|
public Date getLastSearched() {
|
||||||
|
// warning: Date objects are mutable.
|
||||||
|
return (Date)lastSearched.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the last searched date
|
||||||
|
* @param lastSearched Last searched date
|
||||||
|
*/
|
||||||
|
public void setLastSearched(Date lastSearched) {
|
||||||
|
this.lastSearched = lastSearched;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the content URI for this query
|
||||||
|
* @return content URI
|
||||||
|
*/
|
||||||
|
public Uri getContentUri() {
|
||||||
|
return contentUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the content URI - marking this query as already saved in the database
|
||||||
|
*
|
||||||
|
* @param contentUri the content URI
|
||||||
|
*/
|
||||||
|
public void setContentUri(Uri contentUri) {
|
||||||
|
this.contentUri = contentUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
package fr.free.nrw.commons.explore.recentsearches;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||||
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.content.UriMatcher.NO_MATCH;
|
||||||
|
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.ALL_FIELDS;
|
||||||
|
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.COLUMN_ID;
|
||||||
|
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao.Table.TABLE_NAME;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains functions for executing queries for
|
||||||
|
* inserting, searching, deleting, editing recent searches in SqLite DB
|
||||||
|
**/
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri uriForId(int id) {
|
||||||
|
return Uri.parse(BASE_URI.toString() + "/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject DBOpenHelper dbOpenHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functions executes query for searching recent searches in SqLite DB
|
||||||
|
**/
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Override
|
||||||
|
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
|
||||||
|
String[] selectionArgs, String sortOrder) {
|
||||||
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
|
queryBuilder.setTables(TABLE_NAME);
|
||||||
|
|
||||||
|
int uriType = uriMatcher.match(uri);
|
||||||
|
|
||||||
|
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||||
|
Cursor cursor;
|
||||||
|
|
||||||
|
switch (uriType) {
|
||||||
|
case RECENT_SEARCHES:
|
||||||
|
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
|
||||||
|
null, null, sortOrder);
|
||||||
|
break;
|
||||||
|
case RECENT_SEARCHES_ID:
|
||||||
|
cursor = queryBuilder.query(db,
|
||||||
|
ALL_FIELDS,
|
||||||
|
"_id = ?",
|
||||||
|
new String[]{uri.getLastPathSegment()},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
sortOrder
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown URI" + uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType(@NonNull Uri uri) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functions executes query for inserting a recentSearch object in SqLite DB
|
||||||
|
**/
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Override
|
||||||
|
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
|
||||||
|
int uriType = uriMatcher.match(uri);
|
||||||
|
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||||
|
long id;
|
||||||
|
switch (uriType) {
|
||||||
|
case RECENT_SEARCHES:
|
||||||
|
id = sqlDB.insert(TABLE_NAME, null, contentValues);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown URI: " + uri);
|
||||||
|
}
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return Uri.parse(BASE_URI + "/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functions executes query for deleting a recentSearch object in SqLite DB
|
||||||
|
**/
|
||||||
|
@Override
|
||||||
|
public int delete(@NonNull Uri uri, String s, String[] strings) {
|
||||||
|
int rows;
|
||||||
|
int uriType = uriMatcher.match(uri);
|
||||||
|
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||||
|
switch (uriType) {
|
||||||
|
case RECENT_SEARCHES_ID:
|
||||||
|
Timber.d("Deleting recent searches id %s", uri.getLastPathSegment());
|
||||||
|
rows = db.delete(RecentSearchesDao.Table.TABLE_NAME,
|
||||||
|
"_id = ?",
|
||||||
|
new String[]{uri.getLastPathSegment()}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown URI" + uri);
|
||||||
|
}
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functions executes query for inserting multiple recentSearch objects in SqLite DB
|
||||||
|
**/
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Override
|
||||||
|
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
|
||||||
|
Timber.d("Hello, bulk insert! (RecentSearchesContentProvider)");
|
||||||
|
int uriType = uriMatcher.match(uri);
|
||||||
|
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||||
|
sqlDB.beginTransaction();
|
||||||
|
switch (uriType) {
|
||||||
|
case RECENT_SEARCHES:
|
||||||
|
for (ContentValues value : values) {
|
||||||
|
Timber.d("Inserting! %s", value);
|
||||||
|
sqlDB.insert(TABLE_NAME, null, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown URI: " + uri);
|
||||||
|
}
|
||||||
|
sqlDB.setTransactionSuccessful();
|
||||||
|
sqlDB.endTransaction();
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return values.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functions executes query for updating a particular recentSearch object in SqLite DB
|
||||||
|
**/
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Override
|
||||||
|
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.
|
||||||
|
|
||||||
|
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();
|
||||||
|
int rowsUpdated;
|
||||||
|
switch (uriType) {
|
||||||
|
case RECENT_SEARCHES_ID:
|
||||||
|
if (TextUtils.isEmpty(selection)) {
|
||||||
|
int id = Integer.valueOf(uri.getLastPathSegment());
|
||||||
|
rowsUpdated = sqlDB.update(TABLE_NAME,
|
||||||
|
contentValues,
|
||||||
|
COLUMN_ID + " = ?",
|
||||||
|
new String[]{String.valueOf(id)});
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Parameter `selection` should be empty when updating an ID");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType);
|
||||||
|
}
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return rowsUpdated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,235 @@
|
||||||
|
package fr.free.nrw.commons.explore.recentsearches;
|
||||||
|
|
||||||
|
import android.content.ContentProviderClient;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class doesn't execute queries in database directly instead it contains the logic behind
|
||||||
|
* inserting, deleting, searching data from recent searches database.
|
||||||
|
**/
|
||||||
|
public class RecentSearchesDao {
|
||||||
|
|
||||||
|
private final Provider<ContentProviderClient> clientProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RecentSearchesDao(@Named("recentsearch") Provider<ContentProviderClient> clientProvider) {
|
||||||
|
this.clientProvider = clientProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on click of media/ categories for storing them in recent searches
|
||||||
|
* @param recentSearch a recent searches object that is to be added in SqLite DB
|
||||||
|
*/
|
||||||
|
public void save(RecentSearch recentSearch) {
|
||||||
|
ContentProviderClient db = clientProvider.get();
|
||||||
|
try {
|
||||||
|
if (recentSearch.getContentUri() == null) {
|
||||||
|
recentSearch.setContentUri(db.insert(RecentSearchesContentProvider.BASE_URI, toContentValues(recentSearch)));
|
||||||
|
} else {
|
||||||
|
db.update(recentSearch.getContentUri(), toContentValues(recentSearch), null, null);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
db.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on confirmation of delete recent searches.
|
||||||
|
* It deletes latest 10 recent searches from the database
|
||||||
|
* @param recentSearchesStringList list of recent searches to be deleted
|
||||||
|
*/
|
||||||
|
public void deleteAll(List<String> recentSearchesStringList) {
|
||||||
|
ContentProviderClient db = clientProvider.get();
|
||||||
|
for (String recentSearchName : recentSearchesStringList) {
|
||||||
|
try {
|
||||||
|
RecentSearch recentSearch = find(recentSearchName);
|
||||||
|
if (recentSearch.getContentUri() == null) {
|
||||||
|
throw new RuntimeException("tried to delete item with no content URI");
|
||||||
|
} else {
|
||||||
|
Log.d("QUERY_NAME",recentSearch.getContentUri()+"- delete tried");
|
||||||
|
db.delete(recentSearch.getContentUri(), null, null);
|
||||||
|
Log.d("QUERY_NAME",recentSearch.getQuery()+"- query deleted");
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.d("Exception",e+"- query deleted");
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
db.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find persisted search query in database, based on its name.
|
||||||
|
* @param name Search query Ex- "butterfly"
|
||||||
|
* @return recently searched query from database, or null if not found
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public RecentSearch find(String name) {
|
||||||
|
Cursor cursor = null;
|
||||||
|
ContentProviderClient db = clientProvider.get();
|
||||||
|
try {
|
||||||
|
cursor = db.query(
|
||||||
|
RecentSearchesContentProvider.BASE_URI,
|
||||||
|
Table.ALL_FIELDS,
|
||||||
|
Table.COLUMN_NAME + "=?",
|
||||||
|
new String[]{name},
|
||||||
|
null);
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
return fromCursor(cursor);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// This feels lazy, but to hell with checked exceptions. :)
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
db.release();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve recently-searched queries, ordered by descending date.
|
||||||
|
* @return a list containing recent searches
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public List<String> recentSearches(int limit) {
|
||||||
|
List<String> items = new ArrayList<>();
|
||||||
|
Cursor cursor = null;
|
||||||
|
ContentProviderClient db = clientProvider.get();
|
||||||
|
try {
|
||||||
|
cursor = db.query( RecentSearchesContentProvider.BASE_URI, Table.ALL_FIELDS,
|
||||||
|
null, new String[]{}, Table.COLUMN_LAST_USED + " DESC");
|
||||||
|
// fixme add a limit on the original query instead of falling out of the loop?
|
||||||
|
while (cursor != null && cursor.moveToNext() && cursor.getPosition() < limit) {
|
||||||
|
items.add(fromCursor(cursor).getQuery());
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
db.release();
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It creates an Recent Searches object from data stored in the SQLite DB by using cursor
|
||||||
|
* @param cursor
|
||||||
|
* @return RecentSearch object
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
RecentSearch fromCursor(Cursor cursor) {
|
||||||
|
// Hardcoding column positions!
|
||||||
|
return new RecentSearch(
|
||||||
|
RecentSearchesContentProvider.uriForId(cursor.getInt(cursor.getColumnIndex(Table.COLUMN_ID))),
|
||||||
|
cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)),
|
||||||
|
new Date(cursor.getLong(cursor.getColumnIndex(Table.COLUMN_LAST_USED)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains the database table architechture for recent searches,
|
||||||
|
* It also contains queries and logic necessary to the create, update, delete this table.
|
||||||
|
*/
|
||||||
|
private ContentValues toContentValues(RecentSearch recentSearch) {
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
cv.put(RecentSearchesDao.Table.COLUMN_NAME, recentSearch.getQuery());
|
||||||
|
cv.put(RecentSearchesDao.Table.COLUMN_LAST_USED, recentSearch.getLastSearched().getTime());
|
||||||
|
return cv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains the database table architechture for recent searches,
|
||||||
|
* It also contains queries and logic necessary to the create, update, delete this table.
|
||||||
|
*/
|
||||||
|
public static class Table {
|
||||||
|
public static final String TABLE_NAME = "recent_searches";
|
||||||
|
public static final String COLUMN_ID = "_id";
|
||||||
|
static final String COLUMN_NAME = "name";
|
||||||
|
static final String COLUMN_LAST_USED = "last_used";
|
||||||
|
|
||||||
|
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||||
|
public static final String[] ALL_FIELDS = {
|
||||||
|
COLUMN_ID,
|
||||||
|
COLUMN_NAME,
|
||||||
|
COLUMN_LAST_USED,
|
||||||
|
};
|
||||||
|
|
||||||
|
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
||||||
|
|
||||||
|
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
|
||||||
|
+ COLUMN_ID + " INTEGER PRIMARY KEY,"
|
||||||
|
+ COLUMN_NAME + " STRING,"
|
||||||
|
+ COLUMN_LAST_USED + " INTEGER"
|
||||||
|
+ ");";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a RecentSearchesTable in SQLiteDatabase
|
||||||
|
* @param db SQLiteDatabase
|
||||||
|
*/
|
||||||
|
public static void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method deletes RecentSearchesTable from SQLiteDatabase
|
||||||
|
* @param db SQLiteDatabase
|
||||||
|
*/
|
||||||
|
public static void onDelete(SQLiteDatabase db) {
|
||||||
|
db.execSQL(DROP_TABLE_STATEMENT);
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on migrating from a older version to a newer version
|
||||||
|
* @param db SQLiteDatabase
|
||||||
|
* @param from Version from which we are migrating
|
||||||
|
* @param to Version to which we are migrating
|
||||||
|
*/
|
||||||
|
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||||
|
if (from == to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (from < 6) {
|
||||||
|
// doesn't exist yet
|
||||||
|
from++;
|
||||||
|
onUpdate(db, from, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (from == 6) {
|
||||||
|
// table added in version 7
|
||||||
|
onCreate(db);
|
||||||
|
from++;
|
||||||
|
onUpdate(db, from, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (from == 7) {
|
||||||
|
from++;
|
||||||
|
onUpdate(db, from, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package fr.free.nrw.commons.explore.recentsearches;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the recent searches screen.
|
||||||
|
*/
|
||||||
|
public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
||||||
|
@Inject RecentSearchesDao recentSearchesDao;
|
||||||
|
@BindView(R.id.recent_searches_list) ListView recentSearchesList;
|
||||||
|
List<String> recentSearches;
|
||||||
|
ArrayAdapter adapter;
|
||||||
|
@BindView(R.id.recent_searches_delete_button)
|
||||||
|
ImageView recent_searches_delete_button;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_search_history, container, false);
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
|
recent_searches_delete_button.setOnClickListener(v -> {
|
||||||
|
new AlertDialog.Builder(getContext())
|
||||||
|
.setMessage(getString(R.string.delete_recent_searches_dialog))
|
||||||
|
.setPositiveButton("YES", (dialog, which) -> {
|
||||||
|
recentSearchesDao.deleteAll(recentSearches);
|
||||||
|
Toast.makeText(getContext(),getString(R.string.search_history_deleted),Toast.LENGTH_SHORT).show();
|
||||||
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
|
adapter = new ArrayAdapter<String>(getContext(),R.layout.item_recent_searches, recentSearches);
|
||||||
|
recentSearchesList.setAdapter(adapter);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
dialog.dismiss();
|
||||||
|
})
|
||||||
|
.setNegativeButton("NO", null)
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
adapter = new ArrayAdapter<String>(getContext(),R.layout.item_recent_searches, recentSearches);
|
||||||
|
recentSearchesList.setAdapter(adapter);
|
||||||
|
recentSearchesList.setOnItemClickListener((parent, view, position, id) -> (
|
||||||
|
(SearchActivity)getContext()).updateText(recentSearches.get(position)));
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on back press of activity
|
||||||
|
* so we are updating the list from database to refresh the recent searches list.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called when search query is null to update Recent Searches
|
||||||
|
*/
|
||||||
|
public void updateRecentSearches() {
|
||||||
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
|
adapter = new ArrayAdapter<String>(getContext(),R.layout.item_recent_searches, recentSearches);
|
||||||
|
recentSearchesList.setAdapter(adapter);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,9 +35,6 @@ import java.util.Locale;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import fr.free.nrw.commons.License;
|
import fr.free.nrw.commons.License;
|
||||||
import fr.free.nrw.commons.LicenseList;
|
import fr.free.nrw.commons.LicenseList;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
|
|
@ -45,6 +42,7 @@ import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
import fr.free.nrw.commons.PageTitle;
|
import fr.free.nrw.commons.PageTitle;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||||
import fr.free.nrw.commons.delete.DeleteTask;
|
import fr.free.nrw.commons.delete.DeleteTask;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
|
@ -248,6 +246,10 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... voids) {
|
protected Boolean doInBackground(Void... voids) {
|
||||||
|
// Local files have no filename yet
|
||||||
|
if(media.getFilename() == null) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
extractor.fetch(media.getFilename(), licenseList);
|
extractor.fetch(media.getFilename(), licenseList);
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
|
|
@ -429,17 +431,11 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
textView.setText(catName);
|
textView.setText(catName);
|
||||||
if (categoriesLoaded && categoriesPresent) {
|
if (categoriesLoaded && categoriesPresent) {
|
||||||
textView.setOnClickListener(view -> {
|
textView.setOnClickListener(view -> {
|
||||||
|
// Open Category Details page
|
||||||
String selectedCategoryTitle = "Category:" + catName;
|
String selectedCategoryTitle = "Category:" + catName;
|
||||||
Intent viewIntent = new Intent();
|
Intent intent = new Intent(getContext(), CategoryDetailsActivity.class);
|
||||||
viewIntent.setAction(Intent.ACTION_VIEW);
|
intent.putExtra("categoryName", selectedCategoryTitle);
|
||||||
viewIntent.setData(new PageTitle(selectedCategoryTitle).getCanonicalUri());
|
getContext().startActivity(intent);
|
||||||
//check if web browser available
|
|
||||||
if (viewIntent.resolveActivity(getActivity().getPackageManager()) != null) {
|
|
||||||
startActivity(viewIntent);
|
|
||||||
} else {
|
|
||||||
Toast toast = Toast.makeText(getContext(), getString(R.string.no_web_browser), LENGTH_SHORT);
|
|
||||||
toast.show();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@ package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.DownloadManager;
|
import android.app.DownloadManager;
|
||||||
import android.app.WallpaperManager;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.DataSetObserver;
|
import android.database.DataSetObserver;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.os.Handler;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
|
@ -28,8 +27,6 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
@ -50,7 +47,6 @@ import static android.content.Context.DOWNLOAD_SERVICE;
|
||||||
import static android.content.Intent.ACTION_VIEW;
|
import static android.content.Intent.ACTION_VIEW;
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
|
|
||||||
|
|
||||||
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
|
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
|
||||||
|
|
||||||
|
|
@ -127,7 +123,10 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
|
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_share_current_image:
|
case R.id.menu_share_current_image:
|
||||||
// Share - intent set in onCreateOptionsMenu, around line 252
|
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
shareIntent.setType("text/plain");
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT, m.getDisplayTitle() + " \n" + m.getFilePageTitle().getCanonicalUri());
|
||||||
|
startActivity(Intent.createChooser(shareIntent, "Share image via..."));
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_browser_current_image:
|
case R.id.menu_browser_current_image:
|
||||||
// View in browser
|
// View in browser
|
||||||
|
|
@ -240,19 +239,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true);
|
menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true);
|
||||||
menu.findItem(R.id.menu_download_current_image).setEnabled(true).setVisible(true);
|
menu.findItem(R.id.menu_download_current_image).setEnabled(true).setVisible(true);
|
||||||
|
|
||||||
// Set ShareActionProvider Intent
|
if (m instanceof Contribution ) {
|
||||||
ShareActionProvider mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menu.findItem(R.id.menu_share_current_image));
|
|
||||||
// On some phones null is returned for some reason:
|
|
||||||
// https://github.com/commons-app/apps-android-commons/issues/413
|
|
||||||
if (mShareActionProvider != null) {
|
|
||||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
|
||||||
shareIntent.setType("text/plain");
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT,
|
|
||||||
m.getDisplayTitle() + " \n" + m.getFilePageTitle().getCanonicalUri());
|
|
||||||
mShareActionProvider.setShareIntent(shareIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m instanceof Contribution) {
|
|
||||||
Contribution c = (Contribution) m;
|
Contribution c = (Contribution) m;
|
||||||
switch (c.getState()) {
|
switch (c.getState()) {
|
||||||
case Contribution.STATE_FAILED:
|
case Contribution.STATE_FAILED:
|
||||||
|
|
@ -281,7 +268,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showImage(int i) {
|
public void showImage(int i) {
|
||||||
pager.setCurrentItem(i);
|
Handler handler = new Handler();
|
||||||
|
handler.postDelayed(() -> pager.setCurrentItem(i), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
import org.apache.http.params.BasicHttpParams;
|
import org.apache.http.params.BasicHttpParams;
|
||||||
import org.apache.http.params.CoreProtocolPNames;
|
import org.apache.http.params.CoreProtocolPNames;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
import org.mediawiki.api.MWApi;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
@ -35,10 +36,12 @@ import java.net.URL;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
|
@ -51,6 +54,10 @@ import fr.free.nrw.commons.notification.NotificationUtils;
|
||||||
import in.yuvi.http.fluent.Http;
|
import in.yuvi.http.fluent.Http;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue;
|
import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue;
|
||||||
|
|
@ -570,32 +577,26 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method takes categoryName as input and returns a List of Media objects
|
* The method takes categoryName as input and returns a List of Subcategories
|
||||||
* It uses the generator query API to get the images in a category, 10 at a time.
|
* It uses the generator query API to get the subcategories in a category, 500 at a time.
|
||||||
* Uses the query continue values for fetching paginated responses
|
* Uses the query continue values for fetching paginated responses
|
||||||
* @param categoryName Category name as defined on commons
|
* @param categoryName Category name as defined on commons
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public List<Media> getCategoryImages(String categoryName) {
|
public List<String> getSubCategoryList(String categoryName) {
|
||||||
ApiResult apiResult = null;
|
ApiResult apiResult = null;
|
||||||
try {
|
try {
|
||||||
MWApi.RequestBuilder requestBuilder = api.action("query")
|
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||||
.param("generator", "categorymembers")
|
.param("generator", "categorymembers")
|
||||||
.param("format", "xml")
|
.param("format", "xml")
|
||||||
.param("gcmtype", "file")
|
.param("gcmtype","subcat")
|
||||||
.param("gcmtitle", categoryName)
|
.param("gcmtitle", categoryName)
|
||||||
.param("prop", "imageinfo")
|
.param("prop", "info")
|
||||||
.param("gcmlimit", "10")
|
.param("gcmlimit", "500")
|
||||||
.param("iiprop", "url|extmetadata");
|
.param("iiprop", "url|extmetadata");
|
||||||
|
|
||||||
QueryContinue queryContinueValues = getQueryContinueValues(categoryName);
|
|
||||||
if (queryContinueValues != null) {
|
|
||||||
requestBuilder.param("continue", queryContinueValues.getContinueParam());
|
|
||||||
requestBuilder.param("gcmcontinue", queryContinueValues.getGcmContinueParam());
|
|
||||||
}
|
|
||||||
|
|
||||||
apiResult = requestBuilder.get();
|
apiResult = requestBuilder.get();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e("Failed to obtain searchCategories", e);
|
Timber.e("Failed to obtain searchCategories", e);
|
||||||
|
|
@ -613,13 +614,183 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryContinue queryContinue = getQueryContinue(apiResult.getNode("/api/continue").getDocument());
|
NodeList childNodes = categoryImagesNode.getDocument().getChildNodes();
|
||||||
setQueryContinueValues(categoryName, queryContinue);
|
return CategoryImageUtils.getSubCategoryList(childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method takes categoryName as input and returns a List of parent categories
|
||||||
|
* It uses the generator query API to get the parent categories of a category, 500 at a time.
|
||||||
|
* @param categoryName Category name as defined on commons
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<String> getParentCategoryList(String categoryName) {
|
||||||
|
ApiResult apiResult = null;
|
||||||
|
try {
|
||||||
|
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||||
|
.param("generator", "categories")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("titles", categoryName)
|
||||||
|
.param("prop", "info")
|
||||||
|
.param("cllimit", "500")
|
||||||
|
.param("iiprop", "url|extmetadata");
|
||||||
|
|
||||||
|
apiResult = requestBuilder.get();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e("Failed to obtain parent Categories", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||||
|
if (categoryImagesNode == null
|
||||||
|
|| categoryImagesNode.getDocument() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes().getLength() == 0) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeList childNodes = categoryImagesNode.getDocument().getChildNodes();
|
||||||
|
return CategoryImageUtils.getSubCategoryList(childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method takes categoryName as input and returns a List of Media objects
|
||||||
|
* It uses the generator query API to get the images in a category, 10 at a time.
|
||||||
|
* Uses the query continue values for fetching paginated responses
|
||||||
|
* @param categoryName Category name as defined on commons
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<Media> getCategoryImages(String categoryName) {
|
||||||
|
ApiResult apiResult = null;
|
||||||
|
try {
|
||||||
|
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||||
|
.param("generator", "categorymembers")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("gcmtype", "file")
|
||||||
|
.param("gcmtitle", categoryName)
|
||||||
|
.param("gcmsort", "timestamp")//property to sort by;timestamp
|
||||||
|
.param("gcmdir", "desc")//in which direction to sort;descending
|
||||||
|
.param("prop", "imageinfo")
|
||||||
|
.param("gcmlimit", "10")
|
||||||
|
.param("iiprop", "url|extmetadata");
|
||||||
|
|
||||||
|
QueryContinue queryContinueValues = getQueryContinueValues(categoryName);
|
||||||
|
if (queryContinueValues != null) {
|
||||||
|
requestBuilder.param("continue", queryContinueValues.getContinueParam());
|
||||||
|
requestBuilder.param("gcmcontinue", queryContinueValues.getGcmContinueParam());
|
||||||
|
}
|
||||||
|
apiResult = requestBuilder.get();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e("Failed to obtain searchCategories", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||||
|
if (categoryImagesNode == null
|
||||||
|
|| categoryImagesNode.getDocument() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes().getLength() == 0) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult.getNode("/api/continue").getDocument()==null){
|
||||||
|
setQueryContinueValues(categoryName, null);
|
||||||
|
}else {
|
||||||
|
QueryContinue queryContinue = getQueryContinue(apiResult.getNode("/api/continue").getDocument());
|
||||||
|
setQueryContinueValues(categoryName, queryContinue);
|
||||||
|
}
|
||||||
|
|
||||||
NodeList childNodes = categoryImagesNode.getDocument().getChildNodes();
|
NodeList childNodes = categoryImagesNode.getDocument().getChildNodes();
|
||||||
return CategoryImageUtils.getMediaList(childNodes);
|
return CategoryImageUtils.getMediaList(childNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes search keyword as input and returns a list of Media objects filtered using search query
|
||||||
|
* It uses the generator query API to get the images searched using a query, 25 at a time.
|
||||||
|
* @param query keyword to search images on commons
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<Media> searchImages(String query, int offset) {
|
||||||
|
List<ApiResult> imageNodes = null;
|
||||||
|
try {
|
||||||
|
imageNodes = api.action("query")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("list", "search")
|
||||||
|
.param("srwhat", "text")
|
||||||
|
.param("srnamespace", "6")
|
||||||
|
.param("srlimit", "25")
|
||||||
|
.param("sroffset",offset)
|
||||||
|
.param("srsearch", query)
|
||||||
|
.get()
|
||||||
|
.getNodes("/api/query/search/p/@title");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e("Failed to obtain searchImages", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageNodes == null) {
|
||||||
|
return new ArrayList<Media>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Media> images = new ArrayList<>();
|
||||||
|
for (ApiResult imageNode : imageNodes) {
|
||||||
|
String imgName = imageNode.getDocument().getTextContent();
|
||||||
|
images.add(new Media(imgName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method takes search keyword as input and returns a list of categories objects filtered using search query
|
||||||
|
* It uses the generator query API to get the categories searched using a query, 25 at a time.
|
||||||
|
* @param query keyword to search categories on commons
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<String> searchCategory(String query, int offset) {
|
||||||
|
List<ApiResult> categoryNodes = null;
|
||||||
|
try {
|
||||||
|
categoryNodes = api.action("query")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("list", "search")
|
||||||
|
.param("srwhat", "text")
|
||||||
|
.param("srnamespace", "14")
|
||||||
|
.param("srlimit", "25")
|
||||||
|
.param("sroffset",offset)
|
||||||
|
.param("srsearch", query)
|
||||||
|
.get()
|
||||||
|
.getNodes("/api/query/search/p/@title");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e("Failed to obtain searchCategories", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categoryNodes == null) {
|
||||||
|
return new ArrayList<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> categories = new ArrayList<>();
|
||||||
|
for (ApiResult categoryNode : categoryNodes) {
|
||||||
|
String catName = categoryNode.getDocument().getTextContent();
|
||||||
|
categories.add(catName);
|
||||||
|
}
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages
|
* For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages
|
||||||
* https://www.mediawiki.org/wiki/API:Raw_query_continue
|
* https://www.mediawiki.org/wiki/API:Raw_query_continue
|
||||||
|
|
@ -707,7 +878,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
@NonNull
|
@NonNull
|
||||||
public Single<Integer> getUploadCount(String userName) {
|
public Single<Integer> getUploadCount(String userName) {
|
||||||
final String uploadCountUrlTemplate =
|
final String uploadCountUrlTemplate =
|
||||||
wikiMediaToolforgeUrl + "urbanecmbot/uploadsbyuser/uploadsbyuser.py";
|
wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/uploadsbyuser.py";
|
||||||
|
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
String url = String.format(
|
String url = String.format(
|
||||||
|
|
@ -722,12 +893,109 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
* Checks to see if a user is currently blocked from Commons
|
||||||
|
* @return whether or not the user is blocked from Commons
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isUserBlockedFromCommons() {
|
||||||
|
boolean userBlocked = false;
|
||||||
|
try {
|
||||||
|
ApiResult result = api.action("query")
|
||||||
|
.param("action", "query")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("meta", "userinfo")
|
||||||
|
.param("uiprop", "blockinfo")
|
||||||
|
.get();
|
||||||
|
if (result != null) {
|
||||||
|
String blockEnd = result.getString("/api/query/userinfo/@blockexpiry");
|
||||||
|
if (blockEnd.equals("infinite")) {
|
||||||
|
userBlocked = true;
|
||||||
|
} else if (!blockEnd.isEmpty()) {
|
||||||
|
Date endDate = parseMWDate(blockEnd);
|
||||||
|
Date current = new Date();
|
||||||
|
userBlocked = endDate.after(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return userBlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This takes userName as input, which is then used to fetch the feedback/achievements
|
||||||
|
* statistics using OkHttp and JavaRx. This function return JSONObject
|
||||||
|
* @param userName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Single<JSONObject> getAchievements(String userName) {
|
||||||
|
final String fetchAchievementUrlTemplate =
|
||||||
|
wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/feedback.py";
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
String url = String.format(
|
||||||
|
Locale.ENGLISH,
|
||||||
|
fetchAchievementUrlTemplate,
|
||||||
|
new PageTitle(userName).getText());
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
||||||
|
urlBuilder.addQueryParameter("user", userName);
|
||||||
|
Log.i("url", urlBuilder.toString());
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.toString())
|
||||||
|
.build();
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
Response response = client.newCall(request).execute();
|
||||||
|
String jsonData = response.body().string();
|
||||||
|
JSONObject jsonObject = new JSONObject(jsonData);
|
||||||
|
return jsonObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This takes userName as input, which is then used to fetch the no of images deleted
|
||||||
|
* using OkHttp and JavaRx. This function return JSONObject
|
||||||
|
* @param userName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Single<JSONObject> getRevertRespObjectSingle(String userName){
|
||||||
|
final String fetchRevertCountUrlTemplate =
|
||||||
|
wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/feedback.py";
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
String url = String.format(
|
||||||
|
Locale.ENGLISH,
|
||||||
|
fetchRevertCountUrlTemplate,
|
||||||
|
new PageTitle(userName).getText());
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
||||||
|
urlBuilder.addQueryParameter("user", userName);
|
||||||
|
urlBuilder.addQueryParameter("fetch","deletedUploads");
|
||||||
|
Log.i("url", urlBuilder.toString());
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.toString())
|
||||||
|
.build();
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
Response response = client.newCall(request).execute();
|
||||||
|
String jsonData = response.body().string();
|
||||||
|
JSONObject jsonRevertObject = new JSONObject(jsonData);
|
||||||
|
return jsonRevertObject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Date parseMWDate(String mwDate) {
|
private Date parseMWDate(String mwDate) {
|
||||||
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
|
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
|
||||||
|
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
try {
|
try {
|
||||||
return isoFormat.parse(mwDate);
|
return isoFormat.parse(mwDate);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package fr.free.nrw.commons.mwapi;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -41,6 +43,16 @@ public interface MediaWikiApi {
|
||||||
|
|
||||||
List<Media> getCategoryImages(String categoryName);
|
List<Media> getCategoryImages(String categoryName);
|
||||||
|
|
||||||
|
List<String> getSubCategoryList(String categoryName);
|
||||||
|
|
||||||
|
List<String> getParentCategoryList(String categoryName);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
List<Media> searchImages(String title, int offset);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
List<String> searchCategory(String title, int offset);
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
|
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
|
||||||
|
|
||||||
|
|
@ -85,6 +97,14 @@ public interface MediaWikiApi {
|
||||||
@NonNull
|
@NonNull
|
||||||
Single<Integer> getUploadCount(String userName);
|
Single<Integer> getUploadCount(String userName);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Single<JSONObject> getRevertRespObjectSingle(String userName);
|
||||||
|
|
||||||
|
boolean isUserBlockedFromCommons();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Single<JSONObject> getAchievements(String userName);
|
||||||
|
|
||||||
interface ProgressListener {
|
interface ProgressListener {
|
||||||
void onProgress(long transferred, long total);
|
void onProgress(long transferred, long total);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import android.support.design.widget.BottomSheetBehavior;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
@ -24,14 +23,9 @@ import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import android.widget.Toast;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import javax.inject.Named;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.contributions.ContributionController;
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
|
|
|
||||||
165
app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.java
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
package fr.free.nrw.commons.quiz;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
|
import com.facebook.drawee.drawable.ProgressBarDrawable;
|
||||||
|
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||||
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
|
||||||
|
public class QuizActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@BindView(R.id.question_image) SimpleDraweeView imageView;
|
||||||
|
@BindView(R.id.question_text) TextView questionText;
|
||||||
|
@BindView(R.id.question_title) TextView questionTitle;
|
||||||
|
@BindView(R.id.quiz_positive_answer) RadioButton positiveAnswer;
|
||||||
|
@BindView(R.id.quiz_negative_answer) RadioButton negativeAnswer;
|
||||||
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
|
||||||
|
private QuizController quizController = new QuizController();
|
||||||
|
private ArrayList<QuizQuestion> quiz = new ArrayList<QuizQuestion>();
|
||||||
|
private int questionIndex = 0;
|
||||||
|
private int score;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_quiz);
|
||||||
|
Fresco.initialize(this);
|
||||||
|
|
||||||
|
quizController.initialize(this);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
displayQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to move to next question and check whether answer is selected or not
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.next_button)
|
||||||
|
public void setNextQuestion(){
|
||||||
|
if( questionIndex <= quiz.size() && (positiveAnswer.isChecked() || negativeAnswer.isChecked())) {
|
||||||
|
evaluateScore();
|
||||||
|
} else if ( !positiveAnswer.isChecked() && !negativeAnswer.isChecked()){
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
alert.setTitle(getResources().getString(R.string.warning));
|
||||||
|
alert.setMessage(getResources().getString(R.string.warning_for_no_answer));
|
||||||
|
alert.setPositiveButton(R.string.continue_message, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog dialog = alert.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to give warning before ending quiz
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
alert.setTitle(getResources().getString(R.string.warning));
|
||||||
|
alert.setMessage(getResources().getString(R.string.quiz_back_button));
|
||||||
|
alert.setPositiveButton(R.string.continue_message, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
Intent i = new Intent(QuizActivity.this, QuizResultActivity.class);
|
||||||
|
dialog.dismiss();
|
||||||
|
i.putExtra("QuizResult",score);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog dialog = alert.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to display the question
|
||||||
|
*/
|
||||||
|
public void displayQuestion() {
|
||||||
|
quiz = quizController.getQuiz();
|
||||||
|
questionText.setText(quiz.get(questionIndex).getQuestion());
|
||||||
|
questionTitle.setText(getResources().getString(R.string.question)+quiz.get(questionIndex).getQuestionNumber());
|
||||||
|
imageView.setHierarchy(GenericDraweeHierarchyBuilder
|
||||||
|
.newInstance(getResources())
|
||||||
|
.setFailureImage(VectorDrawableCompat.create(getResources(),
|
||||||
|
R.drawable.ic_error_outline_black_24dp, getTheme()))
|
||||||
|
.setProgressBarImage(new ProgressBarDrawable())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
imageView.setImageURI(quiz.get(questionIndex).getUrl());
|
||||||
|
new RadioGroupHelper(this, R.id.quiz_positive_answer, R.id.quiz_negative_answer);
|
||||||
|
positiveAnswer.setChecked(false);
|
||||||
|
negativeAnswer.setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to evaluate score and check whether answer is correct or wrong
|
||||||
|
*/
|
||||||
|
public void evaluateScore() {
|
||||||
|
if((quiz.get(questionIndex).isAnswer() && positiveAnswer.isChecked()) ||
|
||||||
|
(!quiz.get(questionIndex).isAnswer() && negativeAnswer.isChecked()) ){
|
||||||
|
customAlert(getResources().getString(R.string.correct),quiz.get(questionIndex).getAnswerMessage() );
|
||||||
|
score++;
|
||||||
|
} else{
|
||||||
|
customAlert(getResources().getString(R.string.wrong), quiz.get(questionIndex).getAnswerMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to display explanation after each answer, update questionIndex and move to next question
|
||||||
|
* @param title
|
||||||
|
* @param Message
|
||||||
|
*/
|
||||||
|
public void customAlert(String title, String Message) {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setMessage(Message);
|
||||||
|
alert.setPositiveButton(R.string.continue_message, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
questionIndex++;
|
||||||
|
if(questionIndex == quiz.size()){
|
||||||
|
Intent i = new Intent(QuizActivity.this, QuizResultActivity.class);
|
||||||
|
dialog.dismiss();
|
||||||
|
i.putExtra("QuizResult",score);
|
||||||
|
startActivity(i);
|
||||||
|
}else {
|
||||||
|
displayQuestion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog dialog = alert.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
160
app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.java
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
package fr.free.nrw.commons.quiz;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.WelcomeActivity;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetches the number of images uploaded and number of images reverted.
|
||||||
|
* Then it calculates the percentage of the images reverted
|
||||||
|
* if the percentage of images reverted after last quiz exceeds 50% and number of images uploaded is
|
||||||
|
* greater than 50, then quiz is popped up
|
||||||
|
*/
|
||||||
|
public class QuizChecker {
|
||||||
|
|
||||||
|
private int revertCount ;
|
||||||
|
private int totalUploadCount ;
|
||||||
|
private boolean isRevertCountFetched;
|
||||||
|
private boolean isUploadCountFetched;
|
||||||
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
public Context context;
|
||||||
|
private String userName;
|
||||||
|
private MediaWikiApi mediaWikiApi;
|
||||||
|
private SharedPreferences revertPref;
|
||||||
|
private SharedPreferences countPref;
|
||||||
|
|
||||||
|
private final int UPLOAD_COUNT_THRESHOLD = 5;
|
||||||
|
private final String REVERT_PERCENTAGE_FOR_MESSAGE = "50%";
|
||||||
|
private final String REVERT_SHARED_PREFERENCE = "revertCount";
|
||||||
|
private final String UPLOAD_SHARED_PREFERENCE = "uploadCount";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constructor to set the parameters for quiz
|
||||||
|
* @param context
|
||||||
|
* @param userName
|
||||||
|
* @param mediaWikiApi
|
||||||
|
*/
|
||||||
|
public QuizChecker(Context context, String userName, MediaWikiApi mediaWikiApi) {
|
||||||
|
this.context = context;
|
||||||
|
this.userName = userName;
|
||||||
|
this.mediaWikiApi = mediaWikiApi;
|
||||||
|
revertPref = context.getSharedPreferences(REVERT_SHARED_PREFERENCE, Context.MODE_PRIVATE);
|
||||||
|
countPref = context.getSharedPreferences(UPLOAD_SHARED_PREFERENCE,Context.MODE_PRIVATE);
|
||||||
|
setUploadCount();
|
||||||
|
setRevertCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to fet the total number of images uploaded
|
||||||
|
*/
|
||||||
|
private void setUploadCount() {
|
||||||
|
compositeDisposable.add(mediaWikiApi
|
||||||
|
.getUploadCount(userName)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
uploadCount -> setTotalUploadCount(uploadCount),
|
||||||
|
t -> Timber.e(t, "Fetching upload count failed")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set the sub Title of Contibutions Activity and
|
||||||
|
* call function to check for quiz
|
||||||
|
* @param uploadCount
|
||||||
|
*/
|
||||||
|
private void setTotalUploadCount(int uploadCount) {
|
||||||
|
totalUploadCount = uploadCount - countPref.getInt(UPLOAD_SHARED_PREFERENCE,0);
|
||||||
|
if( totalUploadCount < 0){
|
||||||
|
totalUploadCount = 0;
|
||||||
|
countPref.edit().putInt(UPLOAD_SHARED_PREFERENCE,0).apply();
|
||||||
|
}
|
||||||
|
isUploadCountFetched = true;
|
||||||
|
calculateRevertParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To call the API to get reverts count in form of JSONObject
|
||||||
|
*/
|
||||||
|
private void setRevertCount() {
|
||||||
|
compositeDisposable.add(mediaWikiApi
|
||||||
|
.getRevertRespObjectSingle(userName)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
object -> setRevertParameter(object.getInt("deletedUploads"))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to calculate the number of images reverted after previous quiz
|
||||||
|
* @param revertCountFetched
|
||||||
|
*/
|
||||||
|
private void setRevertParameter(int revertCountFetched) {
|
||||||
|
revertCount = revertCountFetched - revertPref.getInt(REVERT_SHARED_PREFERENCE,0);
|
||||||
|
if(revertCount < 0){
|
||||||
|
revertCount = 0;
|
||||||
|
revertPref.edit().putInt(REVERT_SHARED_PREFERENCE, 0).apply();
|
||||||
|
}
|
||||||
|
isRevertCountFetched = true;
|
||||||
|
calculateRevertParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to check whether the criterion to call quiz is satisfied
|
||||||
|
*/
|
||||||
|
private void calculateRevertParameter() {
|
||||||
|
if( revertCount < 0 || totalUploadCount < 0){
|
||||||
|
revertPref.edit().putInt(REVERT_SHARED_PREFERENCE, 0).apply();
|
||||||
|
countPref.edit().putInt(UPLOAD_SHARED_PREFERENCE,0).apply();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isRevertCountFetched && isUploadCountFetched &&
|
||||||
|
totalUploadCount >= UPLOAD_COUNT_THRESHOLD &&
|
||||||
|
(revertCount * 100) / totalUploadCount >= 50) {
|
||||||
|
callQuiz();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alert which prompts to quiz
|
||||||
|
*/
|
||||||
|
public void callQuiz() {
|
||||||
|
Builder alert = new Builder(context);
|
||||||
|
alert.setTitle(context.getResources().getString(R.string.quiz));
|
||||||
|
alert.setMessage(context.getResources().getString(R.string.quiz_alert_message,
|
||||||
|
REVERT_PERCENTAGE_FOR_MESSAGE));
|
||||||
|
alert.setPositiveButton("Proceed", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
int newRevetSharedPrefs = revertCount+ revertPref.getInt(REVERT_SHARED_PREFERENCE,0);
|
||||||
|
revertPref.edit().putInt(REVERT_SHARED_PREFERENCE, newRevetSharedPrefs).apply();
|
||||||
|
int newUploadCount = totalUploadCount + countPref.getInt(UPLOAD_SHARED_PREFERENCE,0);
|
||||||
|
countPref.edit().putInt(UPLOAD_SHARED_PREFERENCE,newUploadCount).apply();
|
||||||
|
Intent i = new Intent(context, WelcomeActivity.class);
|
||||||
|
i.putExtra("isQuiz", true);
|
||||||
|
dialog.dismiss();
|
||||||
|
context.startActivity(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
dialogInterface.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
android.support.v7.app.AlertDialog dialog = alert.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package fr.free.nrw.commons.quiz;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* controls the quiz in the Activity
|
||||||
|
*/
|
||||||
|
public class QuizController {
|
||||||
|
|
||||||
|
ArrayList<QuizQuestion> quiz = new ArrayList<QuizQuestion>();
|
||||||
|
|
||||||
|
private final String URL_FOR_SELFIE = "https://i.imgur.com/0fMYcpM.jpg";
|
||||||
|
private final String URL_FOR_TAJ_MAHAL = "https://upload.wikimedia.org/wikipedia/commons/1/15/Taj_Mahal-03.jpg";
|
||||||
|
private final String URL_FOR_BLURRY_IMAGE = "https://i.imgur.com/Kepb5jR.jpg";
|
||||||
|
private final String URL_FOR_SCREENSHOT = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Social_media_app_mockup_screenshot.svg/500px-Social_media_app_mockup_screenshot.svg.png";
|
||||||
|
private final String URL_FOR_EVENT = "https://upload.wikimedia.org/wikipedia/commons/5/51/HouseBuildingInNorthernVietnam.jpg";
|
||||||
|
|
||||||
|
public void initialize(Context context){
|
||||||
|
QuizQuestion q1 = new QuizQuestion(1,
|
||||||
|
context.getResources().getString(R.string.quiz_question_string),
|
||||||
|
URL_FOR_SELFIE,
|
||||||
|
false,
|
||||||
|
context.getResources().getString(R.string.selfie_answer));
|
||||||
|
quiz.add(q1);
|
||||||
|
|
||||||
|
QuizQuestion q2 = new QuizQuestion(2,
|
||||||
|
context.getResources().getString(R.string.quiz_question_string),
|
||||||
|
URL_FOR_TAJ_MAHAL,
|
||||||
|
true,
|
||||||
|
context.getResources().getString(R.string.taj_mahal_answer));
|
||||||
|
quiz.add(q2);
|
||||||
|
|
||||||
|
QuizQuestion q3 = new QuizQuestion(3,
|
||||||
|
context.getResources().getString(R.string.quiz_question_string),
|
||||||
|
URL_FOR_BLURRY_IMAGE,
|
||||||
|
false,
|
||||||
|
context.getResources().getString(R.string.blurry_image_answer));
|
||||||
|
quiz.add(q3);
|
||||||
|
|
||||||
|
QuizQuestion q4 = new QuizQuestion(4,
|
||||||
|
context.getResources().getString(R.string.quiz_question_string),
|
||||||
|
URL_FOR_SCREENSHOT,
|
||||||
|
false,
|
||||||
|
context.getResources().getString(R.string.screenshot_answer));
|
||||||
|
quiz.add(q4);
|
||||||
|
|
||||||
|
QuizQuestion q5 = new QuizQuestion(5,
|
||||||
|
context.getResources().getString(R.string.quiz_question_string),
|
||||||
|
URL_FOR_EVENT,
|
||||||
|
true,
|
||||||
|
context.getResources().getString(R.string.construction_event_answer));
|
||||||
|
quiz.add(q5);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<QuizQuestion> getQuiz() {
|
||||||
|
return quiz;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
app/src/main/java/fr/free/nrw/commons/quiz/QuizQuestion.java
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package fr.free.nrw.commons.quiz;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class contains information about all the quiz questions
|
||||||
|
*/
|
||||||
|
public class QuizQuestion {
|
||||||
|
private int questionNumber;
|
||||||
|
private String question;
|
||||||
|
private boolean answer;
|
||||||
|
private String url;
|
||||||
|
private String answerMessage;
|
||||||
|
|
||||||
|
QuizQuestion(int questionNumber, String question, String url, boolean answer, String answerMessage){
|
||||||
|
this.questionNumber = questionNumber;
|
||||||
|
this.question = question;
|
||||||
|
this.url = url;
|
||||||
|
this.answer = answer;
|
||||||
|
this.answerMessage = answerMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getUrl() {
|
||||||
|
return Uri.parse(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnswer() {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuestionNumber() {
|
||||||
|
return questionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQuestion() {
|
||||||
|
return question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnswer(boolean answer) {
|
||||||
|
this.answer = answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuestion(String question) {
|
||||||
|
this.question = question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuestionNumber(int questionNumber) {
|
||||||
|
this.questionNumber = questionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnswerMessage() {
|
||||||
|
return answerMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnswerMessage(String answerMessage) {
|
||||||
|
this.answerMessage = answerMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
package fr.free.nrw.commons.quiz;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.dinuscxj.progressbar.CircleProgressBar;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the final score of quiz and congratulates the user
|
||||||
|
*/
|
||||||
|
public class QuizResultActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@BindView(R.id.result_progress_bar) CircleProgressBar resultProgressBar;
|
||||||
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
@BindView(R.id.congratulatory_message) TextView congratulatoryMessageText;
|
||||||
|
|
||||||
|
private final int NUMBER_OF_QUESTIONS = 5;
|
||||||
|
private final int MULTIPLIER_TO_GET_PERCENTAGE = 20;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_quiz_result);
|
||||||
|
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
if( getIntent() != null) {
|
||||||
|
Bundle extras = getIntent().getExtras();
|
||||||
|
int score = extras.getInt("QuizResult");
|
||||||
|
setScore(score);
|
||||||
|
}else{
|
||||||
|
startActivityWithFlags(
|
||||||
|
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to calculate and display percentage and score
|
||||||
|
* @param score
|
||||||
|
*/
|
||||||
|
public void setScore(int score) {
|
||||||
|
int per = score * MULTIPLIER_TO_GET_PERCENTAGE;
|
||||||
|
resultProgressBar.setProgress(per);
|
||||||
|
resultProgressBar.setProgressTextFormatPattern(score +" / " + NUMBER_OF_QUESTIONS);
|
||||||
|
String message = getResources().getString(R.string.congratulatory_message_quiz,per + "%");
|
||||||
|
congratulatoryMessageText.setText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to go to Contibutions Activity
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.quiz_result_next)
|
||||||
|
public void launchContributionActivity(){
|
||||||
|
startActivityWithFlags(
|
||||||
|
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
startActivityWithFlags(
|
||||||
|
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to call intent to an activity
|
||||||
|
* @param context
|
||||||
|
* @param cls
|
||||||
|
* @param flags
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public static <T> void startActivityWithFlags(Context context, Class<T> cls, int... flags) {
|
||||||
|
Intent intent = new Intent(context, cls);
|
||||||
|
for (int flag: flags) {
|
||||||
|
intent.addFlags(flag);
|
||||||
|
}
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to inflate menu
|
||||||
|
* @param menu
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
getMenuInflater().inflate(R.menu.menu_about, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if share option selected then take screenshot and launch alert
|
||||||
|
* @param item
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == R.id.share_app_icon) {
|
||||||
|
View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||||
|
Bitmap screenShot = getScreenShot(rootView);
|
||||||
|
showAlert(screenShot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to store the screenshot of image in bitmap variable temporarily
|
||||||
|
* @param view
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Bitmap getScreenShot(View view) {
|
||||||
|
View screenView = view.getRootView();
|
||||||
|
screenView.setDrawingCacheEnabled(true);
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(screenView.getDrawingCache());
|
||||||
|
screenView.setDrawingCacheEnabled(false);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* share the screenshot through social media
|
||||||
|
* @param bitmap
|
||||||
|
*/
|
||||||
|
void shareScreen(Bitmap bitmap) {
|
||||||
|
try {
|
||||||
|
File file = new File(this.getExternalCacheDir(),"screen.png");
|
||||||
|
FileOutputStream fOut = new FileOutputStream(file);
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
|
||||||
|
fOut.flush();
|
||||||
|
fOut.close();
|
||||||
|
file.setReadable(true, false);
|
||||||
|
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
|
||||||
|
intent.setType("image/png");
|
||||||
|
startActivity(Intent.createChooser(intent, "Share image via"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It display the alertDialog with Image of screenshot
|
||||||
|
* @param screenshot
|
||||||
|
*/
|
||||||
|
public void showAlert(Bitmap screenshot) {
|
||||||
|
AlertDialog.Builder alertadd = new AlertDialog.Builder(QuizResultActivity.this);
|
||||||
|
LayoutInflater factory = LayoutInflater.from(QuizResultActivity.this);
|
||||||
|
final View view = factory.inflate(R.layout.image_alert_layout, null);
|
||||||
|
ImageView screenShotImage = (ImageView) view.findViewById(R.id.alert_image);
|
||||||
|
screenShotImage.setImageBitmap(screenshot);
|
||||||
|
TextView shareMessage = (TextView) view.findViewById(R.id.alert_text);
|
||||||
|
shareMessage.setText(R.string.quiz_result_share_message);
|
||||||
|
alertadd.setView(view);
|
||||||
|
alertadd.setPositiveButton("Proceed", new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
shareScreen(screenshot);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alertadd.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alertadd.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package fr.free.nrw.commons.quiz;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to group to or more radio buttons to ensure
|
||||||
|
* that at a particular time only one of them is selected
|
||||||
|
*/
|
||||||
|
public class RadioGroupHelper {
|
||||||
|
|
||||||
|
public List<CompoundButton> radioButtons = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to group radio buttons
|
||||||
|
* @param radios
|
||||||
|
*/
|
||||||
|
public RadioGroupHelper(RadioButton... radios) {
|
||||||
|
super();
|
||||||
|
for (RadioButton rb : radios) {
|
||||||
|
add(rb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to group radio buttons
|
||||||
|
* @param activity
|
||||||
|
* @param radiosIDs
|
||||||
|
*/
|
||||||
|
public RadioGroupHelper(Activity activity, int... radiosIDs) {
|
||||||
|
this(activity.findViewById(android.R.id.content),radiosIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to group radio buttons
|
||||||
|
* @param rootView
|
||||||
|
* @param radiosIDs
|
||||||
|
*/
|
||||||
|
public RadioGroupHelper(View rootView, int... radiosIDs) {
|
||||||
|
super();
|
||||||
|
for (int radioButtonID : radiosIDs) {
|
||||||
|
add((RadioButton)rootView.findViewById(radioButtonID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(CompoundButton button){
|
||||||
|
this.radioButtons.add(button);
|
||||||
|
button.setOnClickListener(onClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* listener to ensure only one of the radio button is selected
|
||||||
|
*/
|
||||||
|
View.OnClickListener onClickListener = v -> {
|
||||||
|
for (CompoundButton rb : radioButtons) {
|
||||||
|
if(rb != v) rb.setChecked(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ import android.support.v7.widget.Toolbar;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ import fr.free.nrw.commons.BuildConfig;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.WelcomeActivity;
|
import fr.free.nrw.commons.WelcomeActivity;
|
||||||
|
import fr.free.nrw.commons.achievements.AchievementsActivity;
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
|
@ -68,12 +70,19 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
|
|
||||||
View navHeaderView = navigationView.getHeaderView(0);
|
View navHeaderView = navigationView.getHeaderView(0);
|
||||||
TextView username = navHeaderView.findViewById(R.id.username);
|
TextView username = navHeaderView.findViewById(R.id.username);
|
||||||
|
|
||||||
AccountManager accountManager = AccountManager.get(this);
|
AccountManager accountManager = AccountManager.get(this);
|
||||||
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
|
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
|
||||||
if (allAccounts.length != 0) {
|
if (allAccounts.length != 0) {
|
||||||
username.setText(allAccounts[0].name);
|
username.setText(allAccounts[0].name);
|
||||||
}
|
}
|
||||||
|
ImageView userIcon = navHeaderView.findViewById(R.id.user_icon);
|
||||||
|
userIcon.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
drawerLayout.closeDrawer(navigationView);
|
||||||
|
AchievementsActivity.startYourself(NavigationBaseActivity.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initBackButton() {
|
public void initBackButton() {
|
||||||
|
|
@ -82,6 +91,15 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
toggle.setToolbarNavigationClickListener(v -> onBackPressed());
|
toggle.setToolbarNavigationClickListener(v -> onBackPressed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method changes the toolbar icon to back regardless of any conditions that
|
||||||
|
* there is any fragment in the backStack or not
|
||||||
|
*/
|
||||||
|
public void forceInitBackButton() {
|
||||||
|
toggle.setDrawerIndicatorEnabled(false);
|
||||||
|
toggle.setToolbarNavigationClickListener(v -> onBackPressed());
|
||||||
|
}
|
||||||
|
|
||||||
public void initBack() {
|
public void initBack() {
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
@ -156,9 +174,9 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
drawerLayout.closeDrawer(navigationView);
|
drawerLayout.closeDrawer(navigationView);
|
||||||
NotificationActivity.startYourself(this);
|
NotificationActivity.startYourself(this);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_featured_images:
|
case R.id.action_explore:
|
||||||
drawerLayout.closeDrawer(navigationView);
|
drawerLayout.closeDrawer(navigationView);
|
||||||
CategoryImagesActivity.startYourself(this, getString(R.string.title_activity_featured_images), FEATURED_IMAGES_CATEGORY);
|
CategoryImagesActivity.startYourself(this, getString(R.string.title_activity_explore), FEATURED_IMAGES_CATEGORY);
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
|
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
|
||||||
|
|
@ -186,4 +204,16 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles visibility of navigation base toolbar
|
||||||
|
* @param show : Used to handle visibility of toolbar
|
||||||
|
*/
|
||||||
|
public void setNavigationBaseToolbarVisibility(boolean show){
|
||||||
|
if (show){
|
||||||
|
toolbar.setVisibility(View.VISIBLE);
|
||||||
|
}else {
|
||||||
|
toolbar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by seannemann on 6/27/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LongTitleEditTextPreference extends EditTextPreference {
|
||||||
|
public LongTitleEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitleEditTextPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitleEditTextPreference(Context context) {
|
||||||
|
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view)
|
||||||
|
{
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
TextView title= view.findViewById(android.R.id.title);
|
||||||
|
if (title != null) {
|
||||||
|
title.setSingleLine(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by seannemann on 6/27/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LongTitleListPreference extends ListPreference {
|
||||||
|
public LongTitleListPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitleListPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view)
|
||||||
|
{
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
TextView title= view.findViewById(android.R.id.title);
|
||||||
|
if (title != null) {
|
||||||
|
title.setSingleLine(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by seannemann on 6/27/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LongTitlePreference extends Preference {
|
||||||
|
public LongTitlePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitlePreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitlePreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view)
|
||||||
|
{
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
TextView title= view.findViewById(android.R.id.title);
|
||||||
|
if (title != null) {
|
||||||
|
title.setSingleLine(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by seannemann on 6/27/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LongTitlePreferenceCategory extends PreferenceCategory {
|
||||||
|
public LongTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitlePreferenceCategory(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitlePreferenceCategory(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view)
|
||||||
|
{
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
TextView title= view.findViewById(android.R.id.title);
|
||||||
|
if (title != null) {
|
||||||
|
title.setSingleLine(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by seannemann on 6/27/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LongTitleSwitchPreference extends SwitchPreference {
|
||||||
|
public LongTitleSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitleSwitchPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongTitleSwitchPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view)
|
||||||
|
{
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
TextView title= view.findViewById(android.R.id.title);
|
||||||
|
if (title != null) {
|
||||||
|
title.setSingleLine(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapRegionDecoder;
|
import android.graphics.BitmapRegionDecoder;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -17,7 +16,6 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.BaseAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import android.support.annotation.RequiresApi;
|
||||||
import android.support.design.widget.FloatingActionButton;
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.DecelerateInterpolator;
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
|
@ -47,6 +48,7 @@ import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.caching.CacheController;
|
import fr.free.nrw.commons.caching.CacheController;
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
|
|
@ -133,26 +135,40 @@ public class ShareActivity
|
||||||
private long ShortAnimationDuration;
|
private long ShortAnimationDuration;
|
||||||
private boolean isFABOpen = false;
|
private boolean isFABOpen = false;
|
||||||
private float startScaleFinal;
|
private float startScaleFinal;
|
||||||
|
private boolean isZoom = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when user taps the submit button.
|
* Called when user taps the submit button.
|
||||||
* Requests Storage permission, if needed.
|
* Requests Storage permission, if needed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uploadActionInitiated(String title, String description) {
|
public void uploadActionInitiated(String title, String description) {
|
||||||
|
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
if (needsToRequestStoragePermission()) {
|
if (sessionManager.getCurrentAccount() != null) {
|
||||||
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
REQUEST_PERM_ON_SUBMIT_STORAGE);
|
// Check for Storage permission that is required for upload.
|
||||||
|
// Do not allow user to proceed without permission, otherwise will crash
|
||||||
|
if (needsToRequestStoragePermission()) {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
|
REQUEST_PERM_ON_SUBMIT_STORAGE);
|
||||||
|
} else {
|
||||||
|
uploadBegins();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
uploadBegins();
|
uploadBegins();
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
uploadBegins();
|
else //Send user to login activity
|
||||||
|
{
|
||||||
|
Toast.makeText(this, "You need to login first!", Toast.LENGTH_SHORT).show();
|
||||||
|
Intent loginIntent = new Intent(ShareActivity.this, LoginActivity.class);
|
||||||
|
startActivity(loginIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,10 +185,12 @@ public class ShareActivity
|
||||||
!= PackageManager.PERMISSION_GRANTED);
|
!= PackageManager.PERMISSION_GRANTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after permission checks are done.
|
* Called after permission checks are done.
|
||||||
* Gets file metadata for category suggestions, displays toast, caches categories found, calls uploadController
|
* Gets file metadata for category suggestions, displays toast, caches categories found, calls uploadController
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private void uploadBegins() {
|
private void uploadBegins() {
|
||||||
fileObj.processFileCoordinates(locationPermitted);
|
fileObj.processFileCoordinates(locationPermitted);
|
||||||
|
|
||||||
|
|
@ -185,15 +203,18 @@ public class ShareActivity
|
||||||
Timber.d("Cache the categories found");
|
Timber.d("Cache the categories found");
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, wikiDataEntityId, c -> {
|
uploadController.startUpload(title,mediaUri,description,mimeType,source,decimalCoords,wikiDataEntityId,c ->
|
||||||
ShareActivity.this.contribution = c;
|
|
||||||
showPostUpload();
|
{
|
||||||
});
|
ShareActivity.this.contribution = c;
|
||||||
}
|
showPostUpload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts CategorizationFragment after uploadBegins.
|
* Starts CategorizationFragment after uploadBegins.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private void showPostUpload() {
|
private void showPostUpload() {
|
||||||
if (categorizationFragment == null) {
|
if (categorizationFragment == null) {
|
||||||
categorizationFragment = new CategorizationFragment();
|
categorizationFragment = new CategorizationFragment();
|
||||||
|
|
@ -459,6 +480,7 @@ public class ShareActivity
|
||||||
if (CurrentAnimator != null) {
|
if (CurrentAnimator != null) {
|
||||||
CurrentAnimator.cancel();
|
CurrentAnimator.cancel();
|
||||||
}
|
}
|
||||||
|
isZoom = true;
|
||||||
ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit));
|
ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit));
|
||||||
closeFABMenu();
|
closeFABMenu();
|
||||||
mainFab.setVisibility(View.GONE);
|
mainFab.setVisibility(View.GONE);
|
||||||
|
|
@ -475,7 +497,6 @@ public class ShareActivity
|
||||||
|
|
||||||
// Load the high-resolution "zoomed-in" image.
|
// Load the high-resolution "zoomed-in" image.
|
||||||
expandedImageView.setImageBitmap(scaledImage);
|
expandedImageView.setImageBitmap(scaledImage);
|
||||||
|
|
||||||
float startScale = zoomObj.adjustStartEndBounds(startBounds, finalBounds, globalOffset);
|
float startScale = zoomObj.adjustStartEndBounds(startBounds, finalBounds, globalOffset);
|
||||||
|
|
||||||
// Hide the thumbnail and show the zoomed-in view. When the animation
|
// Hide the thumbnail and show the zoomed-in view. When the animation
|
||||||
|
|
@ -547,6 +568,7 @@ public class ShareActivity
|
||||||
if (CurrentAnimator != null) {
|
if (CurrentAnimator != null) {
|
||||||
CurrentAnimator.cancel();
|
CurrentAnimator.cancel();
|
||||||
}
|
}
|
||||||
|
isZoom = false;
|
||||||
zoomOutButton.setVisibility(View.GONE);
|
zoomOutButton.setVisibility(View.GONE);
|
||||||
mainFab.setVisibility(View.VISIBLE);
|
mainFab.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
|
@ -557,6 +579,7 @@ public class ShareActivity
|
||||||
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top))
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top))
|
||||||
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScaleFinal))
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScaleFinal))
|
||||||
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal));
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal));
|
||||||
|
|
||||||
set.setDuration(ShortAnimationDuration);
|
set.setDuration(ShortAnimationDuration);
|
||||||
set.setInterpolator(new DecelerateInterpolator());
|
set.setInterpolator(new DecelerateInterpolator());
|
||||||
set.addListener(new AnimatorListenerAdapter() {
|
set.addListener(new AnimatorListenerAdapter() {
|
||||||
|
|
@ -589,4 +612,18 @@ public class ShareActivity
|
||||||
startActivity(mapIntent);
|
startActivity(mapIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_BACK:
|
||||||
|
if(isZoom) {
|
||||||
|
onZoomOutFabClicked();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode,event);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
@ -13,6 +14,7 @@ import android.text.Editable;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
|
@ -29,6 +31,7 @@ import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
@ -342,4 +345,17 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
.create()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To launch the Commons:Licensing
|
||||||
|
* @param view
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.licenseInfo)
|
||||||
|
public void launchLicenseInfo(View view){
|
||||||
|
Log.i("Language", Locale.getDefault().getLanguage());
|
||||||
|
UrlLicense urlLicense = new UrlLicense();
|
||||||
|
urlLicense.initialize();
|
||||||
|
String url = urlLicense.getLicenseUrl(Locale.getDefault().getLanguage());
|
||||||
|
Utils.handleWebUrl(getActivity() , Uri.parse(url));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,11 @@ import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
|
|
@ -87,18 +91,31 @@ public class UploadController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a new upload task.
|
* Starts a new upload task.
|
||||||
*
|
|
||||||
* @param title the title of the contribution
|
* @param title the title of the contribution
|
||||||
* @param mediaUri the media URI of the contribution
|
* @param mediaUri the media URI of the contribution
|
||||||
* @param description the description of the contribution
|
* @param description the description of the contribution
|
||||||
* @param mimeType the MIME type of the contribution
|
* @param mimeType the MIME type of the contribution
|
||||||
* @param source the source of the contribution
|
* @param source the source of the contribution
|
||||||
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
|
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
|
||||||
|
* @param wikiDataEntityId
|
||||||
* @param onComplete the progress tracker
|
* @param onComplete the progress tracker
|
||||||
*/
|
*/
|
||||||
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) {
|
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) {
|
||||||
Contribution contribution;
|
Contribution contribution;
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: Modify this to include coords
|
||||||
|
contribution = new Contribution(mediaUri, null, title, description, -1,
|
||||||
|
null, null, sessionManager.getCurrentAccount().name,
|
||||||
|
CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
|
||||||
|
|
||||||
|
|
||||||
|
contribution.setTag("mimeType", mimeType);
|
||||||
|
contribution.setSource(source);
|
||||||
|
|
||||||
|
//Calls the next overloaded method
|
||||||
|
startUpload(contribution, onComplete);
|
||||||
|
|
||||||
Timber.d("Wikidata entity ID received from Share activity is %s", wikiDataEntityId);
|
Timber.d("Wikidata entity ID received from Share activity is %s", wikiDataEntityId);
|
||||||
//TODO: Modify this to include coords
|
//TODO: Modify this to include coords
|
||||||
Account currentAccount = sessionManager.getCurrentAccount();
|
Account currentAccount = sessionManager.getCurrentAccount();
|
||||||
|
|
@ -112,12 +129,12 @@ public class UploadController {
|
||||||
null, null, currentAccount.name,
|
null, null, currentAccount.name,
|
||||||
CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
|
CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
|
||||||
|
|
||||||
|
|
||||||
contribution.setTag("mimeType", mimeType);
|
contribution.setTag("mimeType", mimeType);
|
||||||
contribution.setSource(source);
|
contribution.setSource(source);
|
||||||
contribution.setWikiDataEntityId(wikiDataEntityId);
|
contribution.setWikiDataEntityId(wikiDataEntityId);
|
||||||
|
|
||||||
//Calls the next overloaded method
|
|
||||||
startUpload(contribution, onComplete);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.app.PendingIntent;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
@ -18,13 +17,11 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
@ -37,12 +34,7 @@ import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.mwapi.UploadResult;
|
import fr.free.nrw.commons.mwapi.UploadResult;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
|
||||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
|
||||||
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadService extends HandlerService<Contribution> {
|
public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
|
||||||
72
app/src/main/java/fr/free/nrw/commons/upload/UrlLicense.java
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a Util class which provides the necessary token to open the Commons License
|
||||||
|
* info in the user language
|
||||||
|
*/
|
||||||
|
public class UrlLicense {
|
||||||
|
HashMap<String,String> urlLicense = new HashMap<String, String>();
|
||||||
|
public void initialize(){
|
||||||
|
urlLicense.put("en","https://commons.wikimedia.org/wiki/Commons:Licensing");
|
||||||
|
urlLicense.put("ar","https://commons.wikimedia.org/wiki/Commons:Licensing/ar");
|
||||||
|
urlLicense.put("ast","https://commons.wikimedia.org/wiki/Commons:Licensing/ast");
|
||||||
|
urlLicense.put("az","https://commons.wikimedia.org/wiki/Commons:Licensing/az");
|
||||||
|
urlLicense.put("be","https://commons.wikimedia.org/wiki/Commons:Licensing/be");
|
||||||
|
urlLicense.put("bg","https://commons.wikimedia.org/wiki/Commons:Licensing/bg");
|
||||||
|
urlLicense.put("bn","https://commons.wikimedia.org/wiki/Commons:Licensing/bn");
|
||||||
|
urlLicense.put("ca","https://commons.wikimedia.org/wiki/Commons:Licensing/ca");
|
||||||
|
urlLicense.put("cs","https://commons.wikimedia.org/wiki/Commons:Licensing/cs");
|
||||||
|
urlLicense.put("da","https://commons.wikimedia.org/wiki/Commons:Licensing/da");
|
||||||
|
urlLicense.put("de","https://commons.wikimedia.org/wiki/Commons:Licensing/de");
|
||||||
|
urlLicense.put("el","https://commons.wikimedia.org/wiki/Commons:Licensing/el");
|
||||||
|
urlLicense.put("eo","https://commons.wikimedia.org/wiki/Commons:Licensing/eo");
|
||||||
|
urlLicense.put("es","https://commons.wikimedia.org/wiki/Commons:Licensing/es");
|
||||||
|
urlLicense.put("eu","https://commons.wikimedia.org/wiki/Commons:Licensing/eu");
|
||||||
|
urlLicense.put("fa","https://commons.wikimedia.org/wiki/Commons:Licensing/fa");
|
||||||
|
urlLicense.put("fi","https://commons.wikimedia.org/wiki/Commons:Licensing/fi");
|
||||||
|
urlLicense.put("fr","https://commons.wikimedia.org/wiki/Commons:Licensing/fr");
|
||||||
|
urlLicense.put("gl","https://commons.wikimedia.org/wiki/Commons:Licensing/gl");
|
||||||
|
urlLicense.put("gsw","https://commons.wikimedia.org/wiki/Commons:Licensing/gsw");
|
||||||
|
urlLicense.put("he","https://commons.wikimedia.org/wiki/Commons:Licensing/he");
|
||||||
|
urlLicense.put("hi","https://commons.wikimedia.org/wiki/Commons:Licensing/hi");
|
||||||
|
urlLicense.put("hu","https://commons.wikimedia.org/wiki/Commons:Licensing/hu");
|
||||||
|
urlLicense.put("id","https://commons.wikimedia.org/wiki/Commons:Licensing/id");
|
||||||
|
urlLicense.put("is","https://commons.wikimedia.org/wiki/Commons:Licensing/is");
|
||||||
|
urlLicense.put("it","https://commons.wikimedia.org/wiki/Commons:Licensing/it");
|
||||||
|
urlLicense.put("ja","https://commons.wikimedia.org/wiki/Commons:Licensing/ja");
|
||||||
|
urlLicense.put("ka","https://commons.wikimedia.org/wiki/Commons:Licensing/ka");
|
||||||
|
urlLicense.put("km","https://commons.wikimedia.org/wiki/Commons:Licensing/km");
|
||||||
|
urlLicense.put("ko","https://commons.wikimedia.org/wiki/Commons:Licensing/ko");
|
||||||
|
urlLicense.put("ku","https://commons.wikimedia.org/wiki/Commons:Licensing/ku");
|
||||||
|
urlLicense.put("mk","https://commons.wikimedia.org/wiki/Commons:Licensing/mk");
|
||||||
|
urlLicense.put("mr","https://commons.wikimedia.org/wiki/Commons:Licensing/mr");
|
||||||
|
urlLicense.put("ms","https://commons.wikimedia.org/wiki/Commons:Licensing/ms");
|
||||||
|
urlLicense.put("my","https://commons.wikimedia.org/wiki/Commons:Licensing/my");
|
||||||
|
urlLicense.put("nl","https://commons.wikimedia.org/wiki/Commons:Licensing/nl");
|
||||||
|
urlLicense.put("oc","https://commons.wikimedia.org/wiki/Commons:Licensing/oc");
|
||||||
|
urlLicense.put("pl","https://commons.wikimedia.org/wiki/Commons:Licensing/pl");
|
||||||
|
urlLicense.put("pt","https://commons.wikimedia.org/wiki/Commons:Licensing/pt");
|
||||||
|
urlLicense.put("pt-br","https://commons.wikimedia.org/wiki/Commons:Licensing/pt-br");
|
||||||
|
urlLicense.put("ro","https://commons.wikimedia.org/wiki/Commons:Licensing/ro");
|
||||||
|
urlLicense.put("ru","https://commons.wikimedia.org/wiki/Commons:Licensing/ru");
|
||||||
|
urlLicense.put("scn","https://commons.wikimedia.org/wiki/Commons:Licensing/scn");
|
||||||
|
urlLicense.put("sk","https://commons.wikimedia.org/wiki/Commons:Licensing/sk");
|
||||||
|
urlLicense.put("sl","https://commons.wikimedia.org/wiki/Commons:Licensing/sl");
|
||||||
|
urlLicense.put("sv","https://commons.wikimedia.org/wiki/Commons:Licensing/sv");
|
||||||
|
urlLicense.put("tr","https://commons.wikimedia.org/wiki/Commons:Licensing/tr");
|
||||||
|
urlLicense.put("uk","https://commons.wikimedia.org/wiki/Commons:Licensing/uk");
|
||||||
|
urlLicense.put("ur","https://commons.wikimedia.org/wiki/Commons:Licensing/ur");
|
||||||
|
urlLicense.put("vi","https://commons.wikimedia.org/wiki/Commons:Licensing/vi");
|
||||||
|
urlLicense.put("zh","https://commons.wikimedia.org/wiki/Commons:Licensing/zh");
|
||||||
|
}
|
||||||
|
public String getLicenseUrl ( String language){
|
||||||
|
if(urlLicense.containsKey(language)) {
|
||||||
|
return urlLicense.get(language);
|
||||||
|
} else {
|
||||||
|
return urlLicense.get("en");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,14 +3,11 @@ package fr.free.nrw.commons.utils;
|
||||||
import android.app.WallpaperManager;
|
import android.app.WallpaperManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.BitmapRegionDecoder;
|
import android.graphics.BitmapRegionDecoder;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import com.facebook.common.executors.CallerThreadExecutor;
|
import com.facebook.common.executors.CallerThreadExecutor;
|
||||||
import com.facebook.common.references.CloseableReference;
|
import com.facebook.common.references.CloseableReference;
|
||||||
|
|
@ -27,8 +24,6 @@ import java.io.IOException;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by bluesir9 on 3/10/17.
|
* Created by bluesir9 on 3/10/17.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,19 @@ public class ViewUtil {
|
||||||
public static final String SHOWCASE_VIEW_ID_3 = "SHOWCASE_VIEW_ID_3";
|
public static final String SHOWCASE_VIEW_ID_3 = "SHOWCASE_VIEW_ID_3";
|
||||||
|
|
||||||
public static void showSnackbar(View view, int messageResourceId) {
|
public static void showSnackbar(View view, int messageResourceId) {
|
||||||
Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show();
|
if(view.getContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutorUtils.uiExecutor().execute(() -> Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showLongToast(Context context, String text) {
|
public static void showLongToast(Context context, String text) {
|
||||||
Toast.makeText(context, text,
|
if (context == null) {
|
||||||
Toast.LENGTH_LONG).show();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutorUtils.uiExecutor().execute(() -> Toast.makeText(context, text, Toast.LENGTH_LONG).show());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPortrait(Context context) {
|
public static boolean isPortrait(Context context) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.widget;
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager;
|
||||||
|
import android.appwidget.AppWidgetProvider;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
|
import com.prof.rssparser.Article;
|
||||||
|
import com.prof.rssparser.Parser;
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of App Widget functionality.
|
||||||
|
*/
|
||||||
|
public class PicOfDayAppWidget extends AppWidgetProvider {
|
||||||
|
|
||||||
|
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
|
||||||
|
int appWidgetId) {
|
||||||
|
|
||||||
|
// Construct the RemoteViews object
|
||||||
|
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget);
|
||||||
|
|
||||||
|
String urlString = BuildConfig.WIKIMEDIA_API_POTD;
|
||||||
|
Parser parser = new Parser();
|
||||||
|
parser.execute(urlString);
|
||||||
|
parser.onFinish(new Parser.OnTaskCompleted() {
|
||||||
|
@Override
|
||||||
|
public void onTaskCompleted(ArrayList<Article> list) {
|
||||||
|
String desc = list.get(list.size() - 1).getDescription();
|
||||||
|
if (desc != null) {
|
||||||
|
Document document = Jsoup.parse(desc);
|
||||||
|
Elements elements = document.select("img");
|
||||||
|
String imageUrl = elements.get(0).attr("src");
|
||||||
|
if (imageUrl != null && imageUrl.length() > 0) {
|
||||||
|
Picasso.get().load(imageUrl).into(views, R.id.appwidget_image, new int[]{appWidgetId});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instruct the widget manager to update the widget
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||||
|
// There may be multiple widgets active, so update all of them
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Context context) {
|
||||||
|
// Enter relevant functionality for when the first widget is created
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisabled(Context context) {
|
||||||
|
// Enter relevant functionality for when the last widget is disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_arrow_back_primary_24dp.png
Normal file
|
After Width: | Height: | Size: 215 B |
BIN
app/src/main/res/drawable-hdpi/ic_delete_grey_700_24dp.png
Normal file
|
After Width: | Height: | Size: 195 B |
BIN
app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png
Normal file
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 350 B |
BIN
app/src/main/res/drawable-hdpi/ic_search_white_24dp.png
Normal file
|
After Width: | Height: | Size: 492 B |
27
app/src/main/res/drawable-ldpi/ic_explore_24dp.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:pathData="M8.91,2.49a10,10 0,1 0,12.6 6.42A10,10 0,0 0,8.91 2.49ZM14.47,19.61A8,8 0,1 1,19.61 9.53,8 8,0 0,1 14.47,19.61Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M3.35,7.45s2.5,1.82 7.26,0.27 5.71,-4.49 5.71,-4.49l1.11,1.22s-1.61,3.15 -6.36,4.7 -7.91,-0.06 -7.91,-0.06Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M7.52,20.29s1,-2.94 5.71,-4.49 7.26,0.27 7.26,0.27l0.18,-1.64s-3.15,-1.61 -7.91,-0.06 -6.36,4.7 -6.36,4.7Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M3.97,13.82l15.6,-5.07l0.46,1.43l-6.81,2.21l-8.79,2.86l-0.46,-1.43z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M15.8,10.76A9.77,9.77 0,0 0,9.48 4.26l1.25,-1a11.16,11.16 0,0 1,6.49 7,11.16 11.16,0 0,1 -1.12,9.51l-1.6,-0.07A9.77,9.77 0,0 0,15.8 10.76Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M8.2,13.24a9.77,9.77 0,0 1,1.29 -9l-1.6,-0.07A11.16,11.16 0,0 0,6.77 13.7a11.16,11.16 0,0 0,6.49 7l1.25,-1A9.77,9.77 0,0 1,8.2 13.24Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M8.754,4.438l1.427,-0.464l5.068,15.597l-1.427,0.464z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
33
app/src/main/res/drawable-mdpi/badge.xml
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_arrow_back_primary_24dp.png
Normal file
|
After Width: | Height: | Size: 170 B |
BIN
app/src/main/res/drawable-mdpi/ic_delete_grey_700_24dp.png
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png
Normal file
|
After Width: | Height: | Size: 213 B |
|
After Width: | Height: | Size: 248 B |
BIN
app/src/main/res/drawable-mdpi/ic_search_white_24dp.png
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
app/src/main/res/drawable-xhdpi/ic_arrow_back_primary_24dp.png
Normal file
|
After Width: | Height: | Size: 238 B |
BIN
app/src/main/res/drawable-xhdpi/ic_delete_grey_700_24dp.png
Normal file
|
After Width: | Height: | Size: 194 B |
BIN
app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png
Normal file
|
After Width: | Height: | Size: 337 B |
|
After Width: | Height: | Size: 429 B |
BIN
app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_arrow_back_primary_24dp.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_delete_grey_700_24dp.png
Normal file
|
After Width: | Height: | Size: 276 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png
Normal file
|
After Width: | Height: | Size: 498 B |
|
After Width: | Height: | Size: 638 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png
Normal file
|
After Width: | Height: | Size: 899 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_arrow_back_primary_24dp.png
Normal file
|
After Width: | Height: | Size: 366 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_delete_grey_700_24dp.png
Normal file
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 633 B |
|
After Width: | Height: | Size: 830 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |