diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 17f6770d2..a4c944a2d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -96,6 +96,10 @@
android:label="@string/title_activity_featured_images"
android:parentActivityName=".contributions.ContributionsActivity" />
+
+
{
notificationBuilder = new NotificationCompat.Builder(context);
Toast toast = new Toast(context);
toast.setGravity(Gravity.CENTER,0,0);
- toast = Toast.makeText(context,"Trying to nominate "+media.getDisplayTitle()+ " for deletion",Toast.LENGTH_SHORT);
+ toast = Toast.makeText(context,"Trying to nominate "+media.getDisplayTitle()+ " for deletion", Toast.LENGTH_SHORT);
toast.show();
}
@@ -64,7 +64,7 @@ public class DeleteTask extends AsyncTask {
String editToken;
String authCookie;
- String summary = "Nominating " + media.getFilename() +" for deletion.";
+ String summary = context.getString(R.string.nominating_file_for_deletion, media.getFilename());
authCookie = sessionManager.getAuthCookie();
mwApi.setAuthCookie(authCookie);
@@ -97,19 +97,19 @@ public class DeleteTask extends AsyncTask {
publishProgress(1);
mwApi.prependEdit(editToken,fileDeleteString+"\n",
- media.getFilename(),summary);
+ media.getFilename(), summary);
publishProgress(2);
mwApi.edit(editToken,subpageString+"\n",
- "Commons:Deletion_requests/"+media.getFilename(),summary);
+ "Commons:Deletion_requests/"+media.getFilename(), summary);
publishProgress(3);
mwApi.appendEdit(editToken,logPageString+"\n",
- "Commons:Deletion_requests/"+date,summary);
+ "Commons:Deletion_requests/"+date, summary);
publishProgress(4);
mwApi.appendEdit(editToken,userPageString+"\n",
- "User_Talk:"+sessionManager.getCurrentAccount().name,summary);
+ "User_Talk:"+sessionManager.getCurrentAccount().name, summary);
publishProgress(5);
}
catch (Exception e) {
@@ -123,29 +123,21 @@ public class DeleteTask extends AsyncTask {
protected void onProgressUpdate (Integer... values){
super.onProgressUpdate(values);
+ int[] messages = new int[]{
+ R.string.getting_edit_token,
+ R.string.nominate_for_deletion_edit_file_page,
+ R.string.nominate_for_deletion_create_deletion_request,
+ R.string.nominate_for_deletion_edit_deletion_request_log,
+ R.string.nominate_for_deletion_notify_user,
+ R.string.nominate_for_deletion_done
+ };
+
String message = "";
- switch (values[0]){
- case 0:
- message = "Getting token";
- break;
- case 1:
- message = "Adding delete message to file";
- break;
- case 2:
- message = "Creating Delete requests sub-page";
- break;
- case 3:
- message = "Adding file to Delete requests log";
- break;
- case 4:
- message = "Notifying User on Talk page";
- break;
- case 5:
- message = "Done";
- break;
+ if (0 < values[0] && values[0] < messages.length) {
+ message = context.getString(messages[values[0]]);
}
- notificationBuilder.setContentTitle("Nominating "+media.getDisplayTitle()+" for deletion")
+ notificationBuilder.setContentTitle(context.getString(R.string.nominating_file_for_deletion, media.getFilename()))
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(message))
.setSmallIcon(R.drawable.ic_launcher)
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
index 51aa85903..e062dbcc9 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
@@ -10,6 +10,7 @@ import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.category.CategoryImagesActivity;
import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.notification.NotificationActivity;
+import fr.free.nrw.commons.review.ReviewActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import fr.free.nrw.commons.upload.MultipleShareActivity;
import fr.free.nrw.commons.upload.ShareActivity;
@@ -50,4 +51,7 @@ public abstract class ActivityBuilderModule {
@ContributesAndroidInjector
abstract CategoryImagesActivity bindFeaturedImagesActivity();
+
+ @ContributesAndroidInjector
+ abstract ReviewActivity bindReviewActivity();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java
index 91f6d4ccb..99ad9a346 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java
@@ -9,11 +9,11 @@ import dagger.android.support.AndroidSupportInjectionModule;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.MediaWikiImageView;
import fr.free.nrw.commons.auth.LoginActivity;
-import fr.free.nrw.commons.contributions.Contribution;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
import fr.free.nrw.commons.delete.DeleteTask;
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
+import fr.free.nrw.commons.review.CheckCategoryTask;
+import fr.free.nrw.commons.review.SendThankTask;
import fr.free.nrw.commons.settings.SettingsFragment;
import fr.free.nrw.commons.nearby.PlaceRenderer;
@@ -40,6 +40,10 @@ public interface CommonsApplicationComponent extends AndroidInjector getNotifications() {
@@ -616,11 +653,59 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
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
try {
return isoFormat.parse(mwDate);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
+
+ private String formatMWDate(Date date) {
+ return isoFormat.format(date);
+ }
+
+ public Media getRecentRandomImage() throws IOException {
+ Media media = null;
+ int tries = 0;
+ Random r = new Random();
+
+ while (media == null && tries < MAX_RANDOM_TRIES) {
+ Date now = new Date();
+ Date startDate = new Date(now.getTime() - r.nextInt(RANDOM_SECONDS) * 1000L);
+ ApiResult apiResult = null;
+ try {
+ MWApi.RequestBuilder requestBuilder = api.action("query")
+ .param("list", "recentchanges")
+ .param("rcstart", formatMWDate(startDate))
+ .param("rcnamespace", FILE_NAMESPACE)
+ .param("rcprop", "title|ids")
+ .param("rctype", "new|log")
+ .param("rctoponly", "1");
+
+ apiResult = requestBuilder.get();
+ } catch (IOException e) {
+ Timber.e("Failed to obtain recent random", e);
+ }
+ if (apiResult != null) {
+ ApiResult recentChangesNode = apiResult.getNode("/api/query/recentchanges");
+ if (recentChangesNode != null
+ && recentChangesNode.getDocument() != null
+ && recentChangesNode.getDocument().getChildNodes() != null
+ && recentChangesNode.getDocument().getChildNodes().getLength() > 0) {
+ NodeList childNodes = recentChangesNode.getDocument().getChildNodes();
+ String imageTitle = RecentChangesImageUtils.findImageInRecentChanges(childNodes);
+ if (imageTitle != null) {
+ boolean deletionStatus = pageExists("Commons:Deletion_requests/" + imageTitle);
+ if (!deletionStatus) {
+ // strip File: prefix
+ imageTitle = imageTitle.replace("File:", "");
+ media = new Media(imageTitle);
+ }
+ }
+ }
+ }
+ tries++;
+ }
+ return media;
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
index c0bd2fd87..4046530d1 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
@@ -75,7 +75,14 @@ public interface MediaWikiApi {
@NonNull
Single getUploadCount(String userName);
+ boolean thank(String editToken, String revision) throws IOException;
+
+ String firstRevisionOfFile(String filename) throws IOException;
+
interface ProgressListener {
void onProgress(long transferred, long total);
}
+
+ @Nullable
+ Media getRecentRandomImage() throws IOException;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/review/CheckCategoryTask.java b/app/src/main/java/fr/free/nrw/commons/review/CheckCategoryTask.java
new file mode 100644
index 000000000..d2086e5bd
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/review/CheckCategoryTask.java
@@ -0,0 +1,130 @@
+package fr.free.nrw.commons.review;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.support.v4.app.NotificationCompat;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.Media;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.di.ApplicationlessInjection;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import timber.log.Timber;
+
+import static android.support.v4.app.NotificationCompat.DEFAULT_ALL;
+import static android.support.v4.app.NotificationCompat.PRIORITY_HIGH;
+
+// Example code:
+// CheckCategoryTask deleteTask = new CheckCategoryTask(getActivity(), media);
+
+public class CheckCategoryTask extends AsyncTask {
+
+ @Inject
+ MediaWikiApi mwApi;
+ @Inject
+ SessionManager sessionManager;
+
+ public static final int NOTIFICATION_CHECK_CATEGORY = 0x101;
+
+ private NotificationManager notificationManager;
+ private NotificationCompat.Builder notificationBuilder;
+ private Context context;
+ private Media media;
+
+ public CheckCategoryTask(Context context, Media media){
+ this.context = context;
+ this.media = media;
+ }
+
+ @Override
+ protected void onPreExecute(){
+ ApplicationlessInjection
+ .getInstance(context.getApplicationContext())
+ .getCommonsApplicationComponent()
+ .inject(this);
+
+ notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationBuilder = new NotificationCompat.Builder(context);
+ Toast toast = new Toast(context);
+ toast.setGravity(Gravity.CENTER,0,0);
+ toast = Toast.makeText(context, context.getString(R.string.check_category_toast, media.getDisplayTitle()), Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ @Override
+ protected Boolean doInBackground(Void ...voids) {
+ publishProgress(0);
+
+ String editToken;
+ String authCookie;
+ String summary = context.getString(R.string.check_category_edit_summary);
+
+ authCookie = sessionManager.getAuthCookie();
+ mwApi.setAuthCookie(authCookie);
+
+ try {
+ editToken = mwApi.getEditToken();
+ if (editToken.equals("+\\")) {
+ return false;
+ }
+ publishProgress(1);
+
+ mwApi.appendEdit(editToken, "\n{{subst:chc}}\n", media.getFilename(), summary);
+ publishProgress(2);
+ }
+ catch (Exception e) {
+ Timber.d(e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onProgressUpdate (Integer... values){
+ super.onProgressUpdate(values);
+
+ int[] messages = new int[]{R.string.getting_edit_token, R.string.check_category_adding_template};
+ String message = "";
+ if (0 < values[0] && values[0] < messages.length) {
+ message = context.getString(messages[values[0]]);
+ }
+
+ notificationBuilder.setContentTitle(context.getString(R.string.check_category_notification_title, media.getDisplayTitle()))
+ .setStyle(new NotificationCompat.BigTextStyle()
+ .bigText(message))
+ .setSmallIcon(R.drawable.ic_launcher)
+ .setProgress(messages.length, values[0], false)
+ .setOngoing(true);
+ notificationManager.notify(NOTIFICATION_CHECK_CATEGORY, notificationBuilder.build());
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ String message = "";
+ String title = "";
+
+ if (result){
+ title = context.getString(R.string.check_category_success_title);
+ message = context.getString(R.string.check_category_success_message, media.getDisplayTitle());
+ }
+ else {
+ title = context.getString(R.string.check_category_failure_title);
+ message = context.getString(R.string.check_category_failure_message, media.getDisplayTitle());
+ }
+
+ notificationBuilder.setDefaults(DEFAULT_ALL)
+ .setContentTitle(title)
+ .setStyle(new NotificationCompat.BigTextStyle()
+ .bigText(message))
+ .setSmallIcon(R.drawable.ic_launcher)
+ .setProgress(0,0,false)
+ .setOngoing(false)
+ .setPriority(PRIORITY_HIGH);
+ notificationManager.notify(NOTIFICATION_CHECK_CATEGORY, notificationBuilder.build());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java
new file mode 100644
index 000000000..4d51b97f3
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java
@@ -0,0 +1,173 @@
+package fr.free.nrw.commons.review;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import android.os.Handler;
+import android.support.design.widget.NavigationView;
+import android.support.v4.view.ViewPager;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.widget.Toolbar;
+
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+
+import com.viewpagerindicator.CirclePageIndicator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import fr.free.nrw.commons.Media;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.auth.AuthenticatedActivity;
+import fr.free.nrw.commons.mwapi.MediaResult;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * Created by root on 18.05.2018.
+ */
+
+public class ReviewActivity extends AuthenticatedActivity {
+
+ @BindView(R.id.toolbar)
+ Toolbar toolbar;
+ @BindView(R.id.navigation_view)
+ NavigationView navigationView;
+ @BindView(R.id.drawer_layout)
+ DrawerLayout drawerLayout;
+
+ @BindView(R.id.reviewPager)
+ ViewPager pager;
+
+ @Inject MediaWikiApi mwApi;
+
+ private ReviewPagerAdapter reviewPagerAdapter;
+
+ //private ReviewCallback reviewCallback;
+ private ReviewController reviewController;
+
+ @BindView(R.id.reviewPagerIndicator)
+ public CirclePageIndicator pagerIndicator;
+
+ @Override
+ protected void onAuthCookieAcquired(String authCookie) {
+
+ }
+
+ @Override
+ protected void onAuthFailure() {
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_review);
+ ButterKnife.bind(this);
+ initDrawer();
+
+ reviewController = new ReviewController();
+
+
+ reviewPagerAdapter = new ReviewPagerAdapter(getSupportFragmentManager());
+ pager.setAdapter(reviewPagerAdapter);
+ reviewPagerAdapter.getItem(0);
+ pagerIndicator.setViewPager(pager);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.review_randomizer_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+
+ if (id == R.id.action_review_randomizer) {
+ Observable.fromCallable(() -> {
+ Media result = null;
+ try {
+ result = mwApi.getRecentRandomImage();
+
+ //String thumBaseUrl = Utils.makeThumbBaseUrl(result.getFilename());
+ //reviewPagerAdapter.currentThumbBasedUrl = thumBaseUrl;
+
+ //Log.d("review", result.getWikiSource());
+
+ } catch (IOException e) {
+ Log.d("review", e.toString());
+ }
+ return result;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::updateImage);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateImage(Media result) {
+ reviewController.onImageRefreshed(result.getFilename()); //file name is updated
+ reviewPagerAdapter.updateFilename();
+ pager.setCurrentItem(0);
+ Observable.fromCallable(() -> {
+ MediaResult media = mwApi.fetchMediaByFilename("File:" + result.getFilename());
+ return MediaDataExtractorUtil.extractCategories(media.getWikiSource());
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::updateCategories);
+ }
+
+ private void updateCategories(ArrayList categories) {
+ reviewController.onCategoriesRefreshed(categories);
+ reviewPagerAdapter.updateCategories();
+ }
+
+ /**
+ * References ReviewPagerAdapter to null before the activity is destroyed
+ */
+ @Override
+ public void onDestroy() {
+ //adapter.setCallback(null);
+ super.onDestroy();
+ }
+
+ /**
+ * Consumers should be simply using this method to use this activity.
+ * @param context
+ * @param title Page title
+ */
+ public static void startYourself(Context context, String title) {
+ Intent reviewActivity = new Intent(context, ReviewActivity.class);
+ context.startActivity(reviewActivity);
+ }
+
+ interface ReviewCallback {
+ void onImageRefreshed(String itemTitle);
+ void onQuestionChanged();
+ void onSurveyFinished();
+ void onImproperImageReported();
+ void onLicenceViolationReported();
+ void onWrongCategoryReported();
+ void onThankSent();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewController.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewController.java
new file mode 100644
index 000000000..2bde33ecd
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewController.java
@@ -0,0 +1,52 @@
+package fr.free.nrw.commons.review;
+
+import java.util.ArrayList;
+
+/**
+ * Created by root on 19.05.2018.
+ */
+
+public class ReviewController implements ReviewActivity.ReviewCallback {
+ public static String fileName;
+ protected static ArrayList categories;
+
+ @Override
+ public void onImageRefreshed(String fileName) {
+ ReviewController.fileName = fileName;
+ ReviewController.categories = new ArrayList<>();
+ }
+
+ public void onCategoriesRefreshed(ArrayList categories) {
+ ReviewController.categories = categories;
+ }
+
+ @Override
+ public void onQuestionChanged() {
+
+ }
+
+ @Override
+ public void onSurveyFinished() {
+
+ }
+
+ @Override
+ public void onImproperImageReported() {
+
+ }
+
+ @Override
+ public void onLicenceViolationReported() {
+
+ }
+
+ @Override
+ public void onWrongCategoryReported() {
+
+ }
+
+ @Override
+ public void onThankSent() {
+
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.java
new file mode 100644
index 000000000..bab8df6df
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.java
@@ -0,0 +1,92 @@
+package fr.free.nrw.commons.review;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.facebook.drawee.view.SimpleDraweeView;
+
+import java.util.ArrayList;
+
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
+
+/**
+ * Created by root on 19.05.2018.
+ */
+
+public class ReviewImageFragment extends CommonsDaggerSupportFragment {
+
+ public static final int SPAM = 0;
+ public static final int COPYRIGHT = 1;
+ public static final int CATEGORY = 2;
+
+ private int position;
+ private String fileName;
+ private String catString;
+ private View catsView;
+ private SimpleDraweeView simpleDraweeView;
+
+ public void update(int position, String fileName) {
+ this.position = position;
+ this.fileName = fileName;
+
+ if (simpleDraweeView!=null) {
+ simpleDraweeView.setImageURI(Utils.makeThumbBaseUrl(fileName));
+ }
+ }
+
+ public void updateCategories(Iterable categories) {
+ catString = TextUtils.join(", ", categories);
+ if (catsView != null) {
+ ((TextView) catsView).setText(catString);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ position = getArguments().getInt("position");
+ View layoutView = inflater.inflate(R.layout.fragment_review_image, container,
+ false);
+ View textView = layoutView.findViewById(R.id.reviewQuestion);
+ catsView = layoutView.findViewById(R.id.reviewCategories);
+ String question;
+ switch(position) {
+ case COPYRIGHT:
+ question = getString(R.string.review_copyright);
+ break;
+ case CATEGORY:
+ question = getString(R.string.review_category);
+ catsView.setVisibility(View.VISIBLE);
+ break;
+ case SPAM:
+ question = getString(R.string.review_spam);
+ break;
+ default:
+ question = "How did we get here?";
+ }
+ ((TextView) textView).setText(question);
+ simpleDraweeView = layoutView.findViewById(R.id.imageView);
+
+ if (fileName != null) {
+ simpleDraweeView.setImageURI(Utils.makeThumbBaseUrl(fileName));
+ }
+ if (catString != null) {
+ ((TextView) catsView).setText(catString);
+ }
+ return layoutView;
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.java
new file mode 100644
index 000000000..06d07c7c9
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.java
@@ -0,0 +1,51 @@
+package fr.free.nrw.commons.review;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+
+/**
+ * Created by nes on 19.05.2018.
+ */
+
+public class ReviewPagerAdapter extends FragmentStatePagerAdapter {
+ private int currentPosition;
+ ReviewImageFragment[] reviewImageFragments;
+
+
+ public ReviewPagerAdapter(FragmentManager fm) {
+ super(fm);
+ reviewImageFragments = new ReviewImageFragment[] {
+ new ReviewImageFragment(),
+ new ReviewImageFragment(),
+ new ReviewImageFragment()
+ };
+ }
+
+ @Override
+ public int getCount() {
+ return 3;
+ }
+
+ public void updateFilename() {
+ for (int i = 0; i < getCount(); i++) {
+ ReviewImageFragment fragment = reviewImageFragments[i];
+ fragment.update(i, ReviewController.fileName);
+ }
+ }
+
+ public void updateCategories() {
+ ReviewImageFragment categoryFragment = reviewImageFragments[ReviewImageFragment.CATEGORY];
+ categoryFragment.updateCategories(ReviewController.categories);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ Bundle bundle = new Bundle();
+ bundle.putInt("position", position);
+ reviewImageFragments[position].setArguments(bundle);
+ return reviewImageFragments[position];
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/review/SendThankTask.java b/app/src/main/java/fr/free/nrw/commons/review/SendThankTask.java
new file mode 100644
index 000000000..0f723217f
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/review/SendThankTask.java
@@ -0,0 +1,140 @@
+package fr.free.nrw.commons.review;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.support.v4.app.NotificationCompat;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.Media;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.di.ApplicationlessInjection;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import timber.log.Timber;
+
+import static android.support.v4.app.NotificationCompat.DEFAULT_ALL;
+import static android.support.v4.app.NotificationCompat.PRIORITY_HIGH;
+
+// example code:
+//
+// media = new Media("File:Iru.png");
+// Observable.fromCallable(() -> mwApi.firstRevisionOfFile(media.getFilename()))
+// .subscribeOn(Schedulers.io())
+// .observeOn(AndroidSchedulers.mainThread())
+// .subscribe(revision -> {
+// SendThankTask task = new SendThankTask(getActivity(), media, revision);
+// task.execute();
+// });
+
+public class SendThankTask extends AsyncTask {
+
+ @Inject
+ MediaWikiApi mwApi;
+ @Inject
+ SessionManager sessionManager;
+
+ public static final int NOTIFICATION_SEND_THANK = 0x102;
+
+ private NotificationManager notificationManager;
+ private NotificationCompat.Builder notificationBuilder;
+ private Context context;
+ private Media media;
+ private String revision;
+
+ public SendThankTask(Context context, Media media, String revision){
+ this.context = context;
+ this.media = media;
+ this.revision = revision;
+ }
+
+ @Override
+ protected void onPreExecute(){
+ ApplicationlessInjection
+ .getInstance(context.getApplicationContext())
+ .getCommonsApplicationComponent()
+ .inject(this);
+
+ notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationBuilder = new NotificationCompat.Builder(context);
+ Toast toast = new Toast(context);
+ toast.setGravity(Gravity.CENTER,0,0);
+ toast = Toast.makeText(context, context.getString(R.string.send_thank_toast, media.getDisplayTitle()), Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ @Override
+ protected Boolean doInBackground(Void ...voids) {
+ publishProgress(0);
+
+ String editToken;
+ String authCookie;
+
+ authCookie = sessionManager.getAuthCookie();
+ mwApi.setAuthCookie(authCookie);
+
+ try {
+ editToken = mwApi.getEditToken();
+ if (editToken.equals("+\\")) {
+ return false;
+ }
+ publishProgress(1);
+
+ mwApi.thank(editToken, revision);
+
+ publishProgress(2);
+ }
+ catch (Exception e) {
+ Timber.d(e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onProgressUpdate (Integer... values){
+ super.onProgressUpdate(values);
+
+ int[] messages = new int[]{R.string.getting_edit_token, R.string.send_thank_send};
+ String message = "";
+ if (0 < values[0] && values[0] < messages.length) {
+ message = context.getString(messages[values[0]]);
+ }
+
+ notificationBuilder.setContentTitle(context.getString(R.string.send_thank_notification_title))
+ .setStyle(new NotificationCompat.BigTextStyle()
+ .bigText(message))
+ .setSmallIcon(R.drawable.ic_launcher)
+ .setProgress(messages.length, values[0], false)
+ .setOngoing(true);
+ notificationManager.notify(NOTIFICATION_SEND_THANK, notificationBuilder.build());
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ String message = "";
+ String title = "";
+
+ if (result){
+ title = context.getString(R.string.send_thank_success_title);
+ message = context.getString(R.string.send_thank_success_message, media.getDisplayTitle());
+ }
+ else {
+ title = context.getString(R.string.send_thank_failure_title);
+ message = context.getString(R.string.send_thank_failure_message, media.getDisplayTitle());
+ }
+
+ notificationBuilder.setDefaults(DEFAULT_ALL)
+ .setContentTitle(title)
+ .setStyle(new NotificationCompat.BigTextStyle()
+ .bigText(message))
+ .setSmallIcon(R.drawable.ic_launcher)
+ .setProgress(0,0,false)
+ .setOngoing(false)
+ .setPriority(PRIORITY_HIGH);
+ notificationManager.notify(NOTIFICATION_SEND_THANK, notificationBuilder.build());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
index 4a7322b57..8fc12d068 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
@@ -30,6 +30,7 @@ import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.category.CategoryImagesActivity;
import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.notification.NotificationActivity;
+import fr.free.nrw.commons.review.ReviewActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import timber.log.Timber;
@@ -160,6 +161,11 @@ public abstract class NavigationBaseActivity extends BaseActivity
drawerLayout.closeDrawer(navigationView);
CategoryImagesActivity.startYourself(this, getString(R.string.title_activity_featured_images), FEATURED_IMAGES_CATEGORY);
return true;
+
+ case R.id.action_review:
+ drawerLayout.closeDrawer(navigationView);
+ ReviewActivity.startYourself(this, getString(R.string.title_activity_review));
+ return true;
default:
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
return false;
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/MediaDataExtractorUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/MediaDataExtractorUtil.java
new file mode 100644
index 000000000..63421a8e4
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/MediaDataExtractorUtil.java
@@ -0,0 +1,28 @@
+package fr.free.nrw.commons.utils;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MediaDataExtractorUtil {
+
+ /**
+ * We could fetch all category links from API, but we actually only want the ones
+ * directly in the page source so they're editable. In the future this may change.
+ *
+ * @param source wikitext source code
+ */
+ public static ArrayList extractCategories(String source) {
+ ArrayList categories = new ArrayList<>();
+ Pattern regex = Pattern.compile("\\[\\[\\s*Category\\s*:([^]]*)\\s*\\]\\]", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = regex.matcher(source);
+ while (matcher.find()) {
+ String cat = matcher.group(1).trim();
+ categories.add(cat);
+ }
+
+ return categories;
+ }
+
+
+}
diff --git a/app/src/main/res/drawable/ic_check_black_24dp.xml b/app/src/main/res/drawable/ic_check_black_24dp.xml
new file mode 100644
index 000000000..3c728c59f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_check_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_refresh_black_24dp.xml b/app/src/main/res/drawable/ic_refresh_black_24dp.xml
new file mode 100644
index 000000000..8229a9a64
--- /dev/null
+++ b/app/src/main/res/drawable/ic_refresh_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/tab_indicator_default.xml b/app/src/main/res/drawable/tab_indicator_default.xml
new file mode 100644
index 000000000..341f4d706
--- /dev/null
+++ b/app/src/main/res/drawable/tab_indicator_default.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/tab_indicator_selected.xml b/app/src/main/res/drawable/tab_indicator_selected.xml
new file mode 100644
index 000000000..41c1bcf73
--- /dev/null
+++ b/app/src/main/res/drawable/tab_indicator_selected.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/tab_selector.xml b/app/src/main/res/drawable/tab_selector.xml
new file mode 100644
index 000000000..001747c31
--- /dev/null
+++ b/app/src/main/res/drawable/tab_selector.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_review.xml b/app/src/main/res/layout/activity_review.xml
new file mode 100644
index 000000000..a0b813f55
--- /dev/null
+++ b/app/src/main/res/layout/activity_review.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_review_image.xml b/app/src/main/res/layout/fragment_review_image.xml
new file mode 100644
index 000000000..7abf88f01
--- /dev/null
+++ b/app/src/main/res/layout/fragment_review_image.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/drawer.xml b/app/src/main/res/menu/drawer.xml
index ae6e0cce2..104bc665c 100644
--- a/app/src/main/res/menu/drawer.xml
+++ b/app/src/main/res/menu/drawer.xml
@@ -15,6 +15,11 @@
android:icon="@drawable/ic_star_black_24dp"
android:title="@string/navigation_item_featured_images"/>
+
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6e30baa10..57eae6ad3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -83,6 +83,7 @@
Settings
Sign Up
Featured Images
+ Peer Review
About
The Wikimedia Commons app is an open-source app created and maintained by grantees and volunteers of the Wikimedia community. The Wikimedia Foundation is not involved in the creation, development, or maintenance of the app.
Wikimedia Commons
@@ -219,6 +220,7 @@
Tutorial
Notifications
Featured
+ Review
Nearby places cannot be displayed without location permissions
no description found
Commons file page
@@ -239,6 +241,8 @@
Error! URL not found
Nominate for Deletion
This image has been nominated for deletion.
+ Nominating %1$s for deletion.
+ Nominating file for deletion: %1$s
See webpage for details
View in Browser
@@ -283,5 +287,31 @@
Share App
Coordinates were not specified during image selection
Error fetching nearby places.
+ Getting token for editing
+ Adding template for category check
+ Requesting category check for %1$s
+ Requesting category check
+ Requesting category check: Success
+ Requesting category check: Failed
+ Successfully requested category check for %1$s
+ Could not request category check for %1$s
+ Requesting category check for %1$s
+ Adding delete message to file
+ Done
+ Notifying User on Talk page
+ Adding file to Delete requests log
+ Creating Delete requests subpage
+ Not sure
+ Sending Thanks: Success
+ Successfully sent thanks to %1$s
+ Failed to send thanks %1$s
+ Sending Thanks: Failure
+ Sending thanks
+ Sending thanks
+ Sending Thanks for %1$s
+
+ Is this a copyright violation?
+ Is this mis-categorized?
+ Is this spam?