mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Include previous Wikimedia hackathon (2018) task, peer review, to codebase (#2602)
* Add new activity to manifest * Create review activity layout base * Add a new menu item to drawer for peer review * Add a top menu with randomizer icon to review activity * Add strings for review button * Add activity to ActivityBuilderModule for injection * Add a new drawer item to start review acitivty * Create base of the Review Activity * Add fragment pager * Add new fragment for injection * Create a fragment pager layout * Wikimedia hackathon 2018 (#1533) * First draft of fn to get random recent image * Use log entries for requests to beta, try to connect refresh button FIXME: runs http request on main thread, breaks * Tweak button connection * Add ReviewController class * Fix fragments * Wmhack2018 (#1534) * tiny fixes * Load pictures into activities * Re-use same class for all review fragments (#1537) And try to add pager indicator * [WIP] category check * [WIP] add on-click actions to ReviewActivity * [WIP] add SendThankTask * Make it beautiful * Add some category stuff back in to review (#1538) * Use standalone category extraction code in MediaDataExtractor * Add categories to category review page * Change category question text sizes * Call randomizer whenever the activity is ready * Add progressbar * [WIP] add DeleteTask.askReasonAndExecute * Fix refresh button string * Typo: "nominate *for* deletion" * Add formatting to categories and put them in the same textView * Pass context and adapters as parameters to controller * Add actions to controller * Make everyting work * Add another fragment to thank * Fix npe * Add missing execute method * Some codes * Add a funy text * More random recent image selection (#1542) time-based randomness is biased - if someone uploaded 100 images in hour, one week ago, and I select a random point in time, their last image is way more likely to come up than anything else. With this, there is still bias towards choosing one of the last N in any burst of uploads (where N is the number of recent changes fetched) but it's a bit better than before. * Create Revision class * Add meaningluf strings * Error handling for review image/category fetch (#1543) * Add information layout for username and filename * Use Single to get firstRevision * try to add username and filename * Ensure caption is shown on every review fragment * Fix build * Fixes missing import * Change button text,show current category, add skip image button * Modify texts, fix night mode issues * Positive Wording * fix landscape issue * Add checkbox popup,rewording * Spelling Correction * Fix merge * Remove commented out code, use lambda * Simplify toolbar include
This commit is contained in:
parent
a1a65d0832
commit
a32ba452ec
33 changed files with 1594 additions and 33 deletions
|
|
@ -131,6 +131,10 @@
|
|||
android:name=".bookmarks.BookmarksActivity"
|
||||
android:label="@string/title_activity_bookmarks" />
|
||||
|
||||
<activity
|
||||
android:name=".review.ReviewActivity"
|
||||
android:label="@string/title_activity_review" />
|
||||
|
||||
<service android:name=".upload.UploadService" />
|
||||
<service
|
||||
android:name=".auth.WikiAccountAuthenticatorService"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import javax.xml.parsers.ParserConfigurationException;
|
|||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.mwapi.MediaResult;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -78,7 +79,7 @@ public class MediaDataExtractor {
|
|||
|
||||
|
||||
// In-page category links are extracted from source, as XML doesn't cover [[links]]
|
||||
extractCategories(result.getWikiSource());
|
||||
categories = MediaDataExtractorUtil.extractCategories(result.getWikiSource());
|
||||
|
||||
// Description template info is extracted from preprocessor XML
|
||||
processWikiParseTree(result.getParseTreeXmlSource(), licenseList);
|
||||
|
|
@ -107,7 +108,7 @@ public class MediaDataExtractor {
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processWikiParseTree(String source, LicenseList licenseList) throws IOException {
|
||||
Document doc;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,25 @@
|
|||
package fr.free.nrw.commons.delete;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationCompat.Builder;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationCompat.Builder;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.Media;
|
||||
|
|
@ -24,6 +27,7 @@ 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 fr.free.nrw.commons.review.ReviewActivity;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static androidx.core.app.NotificationCompat.DEFAULT_ALL;
|
||||
|
|
@ -63,7 +67,7 @@ public class DeleteTask extends AsyncTask<Void, Integer, Boolean> {
|
|||
.setOnlyAlertOnce(true);
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +77,7 @@ public class DeleteTask extends AsyncTask<Void, Integer, Boolean> {
|
|||
|
||||
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);
|
||||
|
|
@ -106,15 +110,15 @@ public class DeleteTask extends AsyncTask<Void, Integer, Boolean> {
|
|||
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",
|
||||
|
|
@ -132,29 +136,21 @@ public class DeleteTask extends AsyncTask<Void, Integer, Boolean> {
|
|||
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)
|
||||
|
|
@ -170,7 +166,7 @@ public class DeleteTask extends AsyncTask<Void, Integer, Boolean> {
|
|||
|
||||
if (result){
|
||||
title += ": Success";
|
||||
message = "Successfully nominated " + media.getDisplayTitle() + " deletion.";
|
||||
message = "Successfully nominated " + media.getDisplayTitle() + " for deletion.";
|
||||
}
|
||||
else {
|
||||
title += ": Failed";
|
||||
|
|
@ -191,4 +187,111 @@ public class DeleteTask extends AsyncTask<Void, Integer, Boolean> {
|
|||
notificationBuilder.setContentIntent(pendingIntent);
|
||||
notificationManager.notify(NOTIFICATION_DELETE, notificationBuilder.build());
|
||||
}
|
||||
|
||||
// TODO: refactor; see MediaDetailsFragment.onDeleteButtonClicked
|
||||
// ReviewActivity will use this
|
||||
public static void askReasonAndExecute(Media media, Context context, String question, String problem) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(context);
|
||||
alert.setTitle(question);
|
||||
|
||||
boolean[] checkedItems = {false , false, false, false};
|
||||
ArrayList<Integer> mUserReason = new ArrayList<>();
|
||||
|
||||
String[] reasonList= {"Reason 1","Reason 2","Reason 3","Reason 4"};
|
||||
|
||||
|
||||
if(problem.equals("spam")){
|
||||
reasonList[0] = "A selfie";
|
||||
reasonList[1] = "Blurry";
|
||||
reasonList[2] = "Nonsense";
|
||||
reasonList[3] = "Other";
|
||||
}
|
||||
else if(problem.equals("copyRightViolation")){
|
||||
reasonList[0] = "Press photo";
|
||||
reasonList[1] = "Random photo from internet";
|
||||
reasonList[2] = "Logo";
|
||||
reasonList[3] = "Other";
|
||||
}
|
||||
|
||||
alert.setMultiChoiceItems(reasonList, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int position, boolean isChecked) {
|
||||
if(isChecked){
|
||||
mUserReason.add(position);
|
||||
}else{
|
||||
mUserReason.remove((Integer.valueOf(position)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
alert.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
|
||||
String reason = "Because it is ";
|
||||
for (int j = 0; j < mUserReason.size(); j++) {
|
||||
reason = reason + reasonList[mUserReason.get(j)];
|
||||
if (j != mUserReason.size() - 1) {
|
||||
reason = reason + ", ";
|
||||
}
|
||||
}
|
||||
|
||||
((ReviewActivity)context).reviewController.swipeToNext();
|
||||
((ReviewActivity)context).runRandomizer();
|
||||
|
||||
DeleteTask deleteTask = new DeleteTask(context, media, reason);
|
||||
deleteTask.execute();
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton("Cancel" , null);
|
||||
|
||||
|
||||
AlertDialog d = alert.create();
|
||||
d.show();
|
||||
|
||||
// AlertDialog.Builder alert = new AlertDialog.Builder(context);
|
||||
// alert.setMessage(question);
|
||||
// final EditText input = ne
|
||||
// w EditText(context);
|
||||
// input.setText(defaultValue);
|
||||
// alert.setView(input);
|
||||
// input.requestFocus();
|
||||
// alert.setPositiveButton(R.string.ok, (dialog, whichButton) -> {
|
||||
// String reason = input.getText().toString();
|
||||
//
|
||||
// ((ReviewActivity)context).reviewController.swipeToNext();
|
||||
// ((ReviewActivity)context).runRandomizer();
|
||||
//
|
||||
// DeleteTask deleteTask = new DeleteTask(context, media, reason);
|
||||
// deleteTask.execute();
|
||||
// });
|
||||
// alert.setNegativeButton(R.string.cancel, (dialog, whichButton) -> {
|
||||
// });
|
||||
// AlertDialog d = alert.create();
|
||||
// input.addTextChangedListener(new TextWatcher() {
|
||||
// private void handleText() {
|
||||
// final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
// if (input.getText().length() == 0) {
|
||||
// okButton.setEnabled(false);
|
||||
// } else {
|
||||
// okButton.setEnabled(true);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void afterTextChanged(Editable arg0) {
|
||||
// handleText();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
// }
|
||||
// });
|
||||
// d.show();
|
||||
// d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(defaultValue.length() > 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import fr.free.nrw.commons.explore.SearchActivity;
|
|||
|
||||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||
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.UploadActivity;
|
||||
|
||||
|
|
@ -64,4 +65,7 @@ public abstract class ActivityBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract BookmarksActivity bindBookmarksActivity();
|
||||
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ReviewActivity bindReviewActivity();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import fr.free.nrw.commons.auth.LoginActivity;
|
|||
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;
|
||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||
import fr.free.nrw.commons.upload.FileProcessor;
|
||||
|
|
@ -42,6 +45,10 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
|
|||
|
||||
void inject(DeleteTask deleteTask);
|
||||
|
||||
void inject(CheckCategoryTask checkCategoryTask);
|
||||
|
||||
void inject(SendThankTask sendThankTask);
|
||||
|
||||
void inject(SettingsFragment fragment);
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
|||
import fr.free.nrw.commons.nearby.NearbyFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyMapFragment;
|
||||
import fr.free.nrw.commons.review.ReviewImageFragment;
|
||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||
|
||||
@Module
|
||||
|
|
@ -67,4 +68,7 @@ public abstract class FragmentBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract BookmarkLocationsFragment bindBookmarkLocationListFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ReviewImageFragment bindReviewOutOfContextFragment();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ package fr.free.nrw.commons.media;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.DataSetObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
|
@ -17,6 +19,7 @@ import android.view.ViewGroup;
|
|||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Spinner;
|
||||
|
|
@ -31,6 +34,7 @@ import java.util.Locale;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
|
@ -414,6 +418,53 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
if(isDeleted) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
//Reviewer correct me if i have misunderstood something over here
|
||||
//But how does this if (delete.getVisibility() == View.VISIBLE) {
|
||||
// enableDeleteButton(true); makes sense ?
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||
alert.setMessage("Why should this fileckathon-2018 be deleted?");
|
||||
final EditText input = new EditText(getActivity());
|
||||
alert.setView(input);
|
||||
input.requestFocus();
|
||||
alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
String reason = input.getText().toString();
|
||||
|
||||
DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason);
|
||||
deleteTask.execute();
|
||||
enableDeleteButton(false);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
}
|
||||
});
|
||||
AlertDialog d = alert.create();
|
||||
input.addTextChangedListener(new TextWatcher() {
|
||||
private void handleText() {
|
||||
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
if (input.getText().length() == 0) {
|
||||
okButton.setEnabled(false);
|
||||
} else {
|
||||
okButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable arg0) {
|
||||
handleText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package fr.free.nrw.commons.media;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class RecentChangesImageUtils {
|
||||
|
||||
private static final String[] imageExtensions = new String[]
|
||||
{".jpg", ".jpeg", ".png"};
|
||||
|
||||
@Nullable
|
||||
public static String findImageInRecentChanges(NodeList childNodes) {
|
||||
String imageTitle;
|
||||
Random r = new Random();
|
||||
int count = childNodes.getLength();
|
||||
// Build a range array
|
||||
int[] randomIndexes = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
randomIndexes[i] = i;
|
||||
}
|
||||
// Then shuffle it
|
||||
for (int i = 0; i < count; i++) {
|
||||
int swapIndex = r.nextInt(count);
|
||||
int temp = randomIndexes[i];
|
||||
randomIndexes[i] = randomIndexes[swapIndex];
|
||||
randomIndexes[swapIndex] = temp;
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
int randomIndex = randomIndexes[i];
|
||||
Element e = (Element) childNodes.item(randomIndex);
|
||||
if (e.getAttribute("type").equals("log") && !e.getAttribute("old_revid").equals("0")) {
|
||||
// For log entries, we only want ones where old_revid is zero, indicating a new file
|
||||
continue;
|
||||
}
|
||||
imageTitle = e.getAttribute("title");
|
||||
|
||||
for (String imageExtension : imageExtensions) {
|
||||
if (imageTitle.toLowerCase().endsWith(imageExtension)) {
|
||||
return imageTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@ package fr.free.nrw.commons.mwapi;
|
|||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
|
@ -34,9 +32,12 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
|
|
@ -44,6 +45,7 @@ import fr.free.nrw.commons.auth.AccountUtil;
|
|||
import fr.free.nrw.commons.category.CategoryImageUtils;
|
||||
import fr.free.nrw.commons.category.QueryContinue;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.media.RecentChangesImageUtils;
|
||||
import fr.free.nrw.commons.notification.Notification;
|
||||
import fr.free.nrw.commons.notification.NotificationUtils;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
|
|
@ -61,6 +63,14 @@ import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue;
|
|||
*/
|
||||
public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||
private static final String THUMB_SIZE = "640";
|
||||
// Give up if no random recent image found after 5 tries
|
||||
private static final int MAX_RANDOM_TRIES = 5;
|
||||
// Random image request is for some time in the past 30 days
|
||||
private static final int RANDOM_SECONDS = 60 * 60 * 24 * 30;
|
||||
// Assuming MW always gives me UTC
|
||||
private static final SimpleDateFormat isoFormat =
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
|
||||
private static final String FILE_NAMESPACE = "6";
|
||||
private AbstractHttpClient httpClient;
|
||||
private CustomMwApi api;
|
||||
private CustomMwApi wikidataApi;
|
||||
|
|
@ -256,6 +266,19 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
.getString("/api/query/pages/page/@_idx")) != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean thank(String editToken, String revision) throws IOException {
|
||||
CustomApiResult res = api.action("thank")
|
||||
.param("rev", revision)
|
||||
.param("token", editToken)
|
||||
.param("source", getUserAgent())
|
||||
.post();
|
||||
String r = res.getString("/api/result/@success");
|
||||
// Does this correctly check the success/failure?
|
||||
// The docs https://www.mediawiki.org/wiki/Extension:Thanks seems unclear about that.
|
||||
return r.equals("success");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException {
|
||||
|
|
@ -563,6 +586,24 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
.getString("/api/query/pages/page/revisions/rev");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Single<Revision> firstRevisionOfFile(String filename) {
|
||||
return Single.fromCallable(() -> {
|
||||
CustomApiResult res = api.action("query")
|
||||
.param("prop", "revisions")
|
||||
.param("rvprop", "timestamp|ids|user")
|
||||
.param("titles", filename)
|
||||
.param("rvdir", "newer")
|
||||
.param("rvlimit", "1")
|
||||
.get();
|
||||
return new Revision(
|
||||
res.getString("/api/query/pages/page/revisions/rev/@revid"),
|
||||
res.getString("/api/query/pages/page/revisions/rev/@user"),
|
||||
filename);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<Notification> getNotifications(boolean archived) {
|
||||
|
|
@ -758,6 +799,50 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
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<CustomApiResult> imageNodes = null;
|
||||
List<CustomApiResult> authorNodes = null;
|
||||
CustomApiResult customApiResult;
|
||||
try {
|
||||
customApiResult= api.action("query")
|
||||
.param("format", "xml")
|
||||
.param("generator", "search")
|
||||
.param("gsrwhat", "text")
|
||||
.param("gsrnamespace", "6")
|
||||
.param("gsrlimit", "25")
|
||||
.param("gsroffset",offset)
|
||||
.param("gsrsearch", query)
|
||||
.param("prop", "imageinfo")
|
||||
.get();
|
||||
imageNodes= customApiResult.getNodes("/api/query/pages/page/@title");
|
||||
authorNodes= customApiResult.getNodes("/api/query/pages/page/imageinfo/ii/@user");
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "Failed to obtain searchImages");
|
||||
}
|
||||
|
||||
if (imageNodes == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<Media> images = new ArrayList<>();
|
||||
|
||||
for (int i=0; i< imageNodes.size();i++){
|
||||
String imgName = imageNodes.get(i).getDocument().getTextContent();
|
||||
Media media = new Media(imgName);
|
||||
media.setCreator(authorNodes.get(i).getDocument().getTextContent());
|
||||
images.add(media);
|
||||
}
|
||||
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.
|
||||
|
|
@ -947,6 +1032,78 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
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 MediaWiki user name
|
||||
// * @return
|
||||
// */
|
||||
// @Override
|
||||
// public Single<FeedbackResponse> 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);
|
||||
// Timber.i("Url %s", urlBuilder.toString());
|
||||
// Request request = new Request.Builder()
|
||||
// .url(urlBuilder.toString())
|
||||
// .build();
|
||||
// Response response = okHttpClient.newCall(request).execute();
|
||||
// if (response != null && response.body() != null && response.isSuccessful()) {
|
||||
// String json = response.body().string();
|
||||
// if (json == null) {
|
||||
// return null;
|
||||
// }
|
||||
// return gson.fromJson(json, FeedbackResponse.class);
|
||||
// }
|
||||
// return null;
|
||||
// });
|
||||
//
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * The method returns the picture of the day
|
||||
// *
|
||||
// * @return Media object corresponding to the picture of the day
|
||||
// */
|
||||
// @Override
|
||||
// @Nullable
|
||||
// public Single<Media> getPictureOfTheDay() {
|
||||
// return Single.fromCallable(() -> {
|
||||
// CustomApiResult apiResult = null;
|
||||
// try {
|
||||
// String template = "Template:Potd/" + DateUtils.getCurrentDate();
|
||||
// CustomMwApi.RequestBuilder requestBuilder = api.action("query")
|
||||
// .param("generator", "images")
|
||||
// .param("format", "xml")
|
||||
// .param("titles", template)
|
||||
// .param("prop", "imageinfo")
|
||||
// .param("iiprop", "url|extmetadata");
|
||||
//
|
||||
// apiResult = requestBuilder.get();
|
||||
// } catch (IOException e) {
|
||||
// Timber.e(e, "Failed to obtain searchCategories");
|
||||
// }
|
||||
//
|
||||
// if (apiResult == null) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// CustomApiResult imageNode = apiResult.getNode("/api/query/pages/page");
|
||||
// if (imageNode == null
|
||||
// || imageNode.getDocument() == null) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// return CategoryImageUtils.getMediaFromPage(imageNode.getDocument());
|
||||
// });
|
||||
// }
|
||||
|
||||
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
|
||||
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
|
@ -967,4 +1124,68 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
Timber.e(e, "Error occurred while logging out");
|
||||
}
|
||||
}
|
||||
|
||||
// @Override public Single<CampaignResponseDTO> getCampaigns() {
|
||||
// return Single.fromCallable(() -> {
|
||||
// Request request = new Request.Builder().url(WIKIMEDIA_CAMPAIGNS_BASE_URL).build();
|
||||
// Response response = okHttpClient.newCall(request).execute();
|
||||
// if (response != null && response.body() != null && response.isSuccessful()) {
|
||||
// String json = response.body().string();
|
||||
// if (json == null) {
|
||||
// return null;
|
||||
// }
|
||||
// return gson.fromJson(json, CampaignResponseDTO.class);
|
||||
// }
|
||||
// return null;
|
||||
// });
|
||||
// }
|
||||
|
||||
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);
|
||||
CustomApiResult apiResult = null;
|
||||
try {
|
||||
CustomMwApi.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(e, "Failed to obtain recent random");
|
||||
}
|
||||
if (apiResult != null) {
|
||||
CustomApiResult 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import java.io.InputStream;
|
|||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||
import fr.free.nrw.commons.notification.Notification;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
|
|
@ -104,7 +105,16 @@ public interface MediaWikiApi {
|
|||
|
||||
void logout();
|
||||
|
||||
// Single<CampaignResponseDTO> getCampaigns();
|
||||
|
||||
boolean thank(String editToken, String revision) throws IOException;
|
||||
|
||||
Single<Revision> firstRevisionOfFile(String filename);
|
||||
|
||||
interface ProgressListener {
|
||||
void onProgress(long transferred, long total);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Media getRecentRandomImage() throws IOException;
|
||||
}
|
||||
|
|
|
|||
15
app/src/main/java/fr/free/nrw/commons/mwapi/Revision.java
Normal file
15
app/src/main/java/fr/free/nrw/commons/mwapi/Revision.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import fr.free.nrw.commons.PageTitle;
|
||||
|
||||
public class Revision {
|
||||
public final String revisionId;
|
||||
public final String username;
|
||||
public final PageTitle pageTitle;
|
||||
|
||||
public Revision(String revisionId, String username, String pageTitle) {
|
||||
this.revisionId = revisionId;
|
||||
this.username = username;
|
||||
this.pageTitle = new PageTitle(pageTitle);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
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;
|
||||
|
||||
|
||||
// Example code:
|
||||
// CheckCategoryTask deleteTask = new CheckCategoryTask(getActivity(), media);
|
||||
|
||||
// TODO: refactor; see DeleteTask and SendThankTask
|
||||
public class CheckCategoryTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
@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(NotificationCompat.DEFAULT_ALL)
|
||||
.setContentTitle(title)
|
||||
.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(message))
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setProgress(0,0,false)
|
||||
.setOngoing(false)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||
notificationManager.notify(NOTIFICATION_CHECK_CATEGORY, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
166
app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java
Normal file
166
app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.viewpagerindicator.CirclePageIndicator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
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 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;
|
||||
|
||||
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)
|
||||
ReviewViewPager reviewPager;
|
||||
|
||||
@BindView(R.id.skip_image)
|
||||
Button skip_image_button;
|
||||
|
||||
@Inject MediaWikiApi mwApi;
|
||||
|
||||
public ReviewPagerAdapter reviewPagerAdapter;
|
||||
|
||||
public 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(this);
|
||||
|
||||
reviewPagerAdapter = new ReviewPagerAdapter(getSupportFragmentManager());
|
||||
reviewPager.setAdapter(reviewPagerAdapter);
|
||||
reviewPagerAdapter.getItem(0);
|
||||
pagerIndicator.setViewPager(reviewPager);
|
||||
|
||||
runRandomizer(); //Run randomizer whenever everything is ready so that a first random image will be added
|
||||
|
||||
skip_image_button.setOnClickListener(view -> runRandomizer());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean runRandomizer() {
|
||||
ProgressBar progressBar = reviewPagerAdapter.reviewImageFragments[reviewPager.getCurrentItem()].progressBar;
|
||||
if (progressBar != null) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
reviewPager.setCurrentItem(0);
|
||||
Observable.fromCallable(() -> {
|
||||
String result = "";
|
||||
try {
|
||||
Media media = mwApi.getRecentRandomImage();
|
||||
if (media != null) {
|
||||
result = media.getFilename();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.e("Error fetching recent random image: " + e.toString());
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::updateImage);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateImage(String fileName) {
|
||||
if (fileName.length() == 0) {
|
||||
ViewUtil.showShortSnackbar(drawerLayout, R.string.error_review);
|
||||
return;
|
||||
}
|
||||
reviewController.onImageRefreshed(fileName); //file name is updated
|
||||
mwApi.firstRevisionOfFile("File:" + fileName)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(revision -> {
|
||||
reviewController.firstRevision = revision;
|
||||
reviewPagerAdapter.updateFileInformation(fileName, revision);
|
||||
});
|
||||
reviewPager.setCurrentItem(0);
|
||||
Observable.fromCallable(() -> {
|
||||
MediaResult media = mwApi.fetchMediaByFilename("File:" + fileName);
|
||||
return MediaDataExtractorUtil.extractCategories(media.getWikiSource());
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::updateCategories, this::categoryFetchError);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void categoryFetchError(Throwable throwable) {
|
||||
Timber.e(throwable, "Error fetching categories");
|
||||
ViewUtil.showShortSnackbar(drawerLayout, R.string.error_review_categories);
|
||||
}
|
||||
|
||||
private void updateCategories(ArrayList<String> categories) {
|
||||
reviewController.onCategoriesRefreshed(categories);
|
||||
reviewPagerAdapter.updateCategories();
|
||||
}
|
||||
|
||||
/**
|
||||
* References ReviewPagerAdapter to null before the activity is destroyed
|
||||
*/
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.delete.DeleteTask;
|
||||
import fr.free.nrw.commons.mwapi.Revision;
|
||||
|
||||
public class ReviewController {
|
||||
private String fileName;
|
||||
@Nullable
|
||||
public Revision firstRevision; // TODO: maybe we can expand this class to include fileName
|
||||
protected static ArrayList<String> categories;
|
||||
|
||||
private ReviewPagerAdapter reviewPagerAdapter;
|
||||
private ViewPager viewPager;
|
||||
private ReviewActivity reviewActivity;
|
||||
|
||||
ReviewController(Context context) {
|
||||
reviewActivity = (ReviewActivity)context;
|
||||
reviewPagerAdapter = reviewActivity.reviewPagerAdapter;
|
||||
viewPager = ((ReviewActivity)context).reviewPager;
|
||||
}
|
||||
|
||||
public void onImageRefreshed(String fileName) {
|
||||
this.fileName = fileName;
|
||||
ReviewController.categories = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void onCategoriesRefreshed(ArrayList<String> categories) {
|
||||
ReviewController.categories = categories;
|
||||
}
|
||||
|
||||
public void swipeToNext() {
|
||||
int nextPos = viewPager.getCurrentItem()+1;
|
||||
if (nextPos <= 3) {
|
||||
viewPager.setCurrentItem(nextPos);
|
||||
} else {
|
||||
reviewActivity.runRandomizer();
|
||||
}
|
||||
}
|
||||
|
||||
public void reportSpam() {
|
||||
DeleteTask.askReasonAndExecute(new Media("File:"+fileName),
|
||||
reviewActivity,
|
||||
reviewActivity.getResources().getString(R.string.review_spam_report_question),
|
||||
reviewActivity.getResources().getString(R.string.review_spam_report_problem));
|
||||
}
|
||||
|
||||
public void reportPossibleCopyRightViolation() {
|
||||
DeleteTask.askReasonAndExecute(new Media("File:"+fileName),
|
||||
reviewActivity,
|
||||
reviewActivity.getResources().getString(R.string.review_c_violation_report_question),
|
||||
reviewActivity.getResources().getString(R.string.review_c_violation_report_problem));
|
||||
}
|
||||
|
||||
public void reportWrongCategory() {
|
||||
new CheckCategoryTask(reviewActivity, new Media("File:"+fileName)).execute();
|
||||
swipeToNext();
|
||||
}
|
||||
|
||||
public void sendThanks() {
|
||||
new SendThankTask(reviewActivity, new Media("File:"+fileName), firstRevision).execute();
|
||||
swipeToNext();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.mwapi.Revision;
|
||||
|
||||
public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
public static final int SPAM = 0;
|
||||
public static final int COPYRIGHT = 1;
|
||||
public static final int CATEGORY = 2;
|
||||
public static final int THANKS = 3;
|
||||
|
||||
private int position;
|
||||
private String fileName;
|
||||
private String catString;
|
||||
|
||||
private View textViewQuestionContext;
|
||||
private View imageCaption;
|
||||
private View textViewQuestion;
|
||||
private SimpleDraweeView simpleDraweeView;
|
||||
|
||||
private Button yesButton;
|
||||
private Button noButton;
|
||||
|
||||
public ProgressBar progressBar;
|
||||
private Revision revision;
|
||||
|
||||
|
||||
public void update(int position, String fileName, Revision revision) {
|
||||
this.position = position;
|
||||
this.fileName = fileName;
|
||||
this.revision = revision;
|
||||
|
||||
fillImageCaption();
|
||||
|
||||
if (simpleDraweeView != null) {
|
||||
simpleDraweeView.setImageURI(Utils.makeThumbBaseUrl(fileName));
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateCategories(Iterable<String> categories) {
|
||||
if (categories != null && isAdded()) {
|
||||
catString = TextUtils.join(", ", categories);
|
||||
if (catString != null && !catString.equals("") && textViewQuestionContext != null) {
|
||||
catString = "<b>" + catString + "</b>";
|
||||
String stringToConvertHtml = String.format(getResources().getString(R.string.review_category_explanation), catString);
|
||||
((TextView) textViewQuestionContext).setText(Html.fromHtml(stringToConvertHtml));
|
||||
} else if (textViewQuestionContext != null) {
|
||||
((TextView) textViewQuestionContext).setText(getResources().getString(R.string.review_no_category));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
progressBar = layoutView.findViewById(R.id.progressBar);
|
||||
textViewQuestion = layoutView.findViewById(R.id.reviewQuestion);
|
||||
textViewQuestionContext = layoutView.findViewById(R.id.reviewQuestionContext);
|
||||
imageCaption = layoutView.findViewById(R.id.imageCaption);
|
||||
yesButton = layoutView.findViewById(R.id.yesButton);
|
||||
noButton = layoutView.findViewById(R.id.noButton);
|
||||
|
||||
fillImageCaption();
|
||||
|
||||
String question, explanation, yesButtonText, noButtonText;
|
||||
switch (position) {
|
||||
case COPYRIGHT:
|
||||
question = getString(R.string.review_copyright);
|
||||
explanation = getString(R.string.review_copyright_explanation);
|
||||
yesButtonText = getString(R.string.review_copyright_yes_button_text);
|
||||
noButtonText = getString(R.string.review_copyright_no_button_text);
|
||||
yesButton.setOnClickListener(view -> {
|
||||
((ReviewActivity) getActivity()).reviewController.reportPossibleCopyRightViolation();
|
||||
});
|
||||
break;
|
||||
case CATEGORY:
|
||||
question = getString(R.string.review_category);
|
||||
explanation = getString(R.string.review_no_category);
|
||||
yesButtonText = getString(R.string.review_category_yes_button_text);
|
||||
noButtonText = getString(R.string.review_category_no_button_text);
|
||||
yesButton.setOnClickListener(view -> {
|
||||
((ReviewActivity) getActivity()).reviewController.reportWrongCategory();
|
||||
});
|
||||
break;
|
||||
case SPAM:
|
||||
question = getString(R.string.review_spam);
|
||||
explanation = getString(R.string.review_spam_explanation);
|
||||
yesButtonText = getString(R.string.review_spam_yes_button_text);
|
||||
noButtonText = getString(R.string.review_spam_no_button_text);
|
||||
yesButton.setOnClickListener(view -> {
|
||||
((ReviewActivity) getActivity()).reviewController.reportSpam();
|
||||
});
|
||||
break;
|
||||
case THANKS:
|
||||
question = getString(R.string.review_thanks);
|
||||
explanation = getString(R.string.review_thanks_explanation, ((ReviewActivity) getActivity()).reviewController.firstRevision.username);
|
||||
yesButtonText = getString(R.string.review_thanks_yes_button_text);
|
||||
noButtonText = getString(R.string.review_thanks_no_button_text);
|
||||
yesButton.setTextColor(Color.parseColor("#228b22"));
|
||||
noButton.setTextColor(Color.parseColor("#116aaa"));
|
||||
yesButton.setOnClickListener(view -> {
|
||||
((ReviewActivity) getActivity()).reviewController.sendThanks();
|
||||
});
|
||||
break;
|
||||
default :
|
||||
question = "How did we get here?";
|
||||
explanation = "No idea.";
|
||||
yesButtonText = "yes";
|
||||
noButtonText = "no";
|
||||
}
|
||||
|
||||
noButton.setOnClickListener(view -> {
|
||||
((ReviewActivity) getActivity()).reviewController.swipeToNext();
|
||||
});
|
||||
|
||||
((TextView) textViewQuestion).setText(question);
|
||||
((TextView) textViewQuestionContext).setText(explanation);
|
||||
yesButton.setText(yesButtonText);
|
||||
noButton.setText(noButtonText);
|
||||
|
||||
if(position==CATEGORY){
|
||||
updateCategories(ReviewController.categories);
|
||||
}
|
||||
|
||||
simpleDraweeView = layoutView.findViewById(R.id.imageView);
|
||||
|
||||
if (fileName != null) {
|
||||
simpleDraweeView.setImageURI(Utils.makeThumbBaseUrl(fileName));
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
return layoutView;
|
||||
}
|
||||
|
||||
private void fillImageCaption() {
|
||||
if (imageCaption != null && fileName != null && revision != null) {
|
||||
((TextView) imageCaption).setText(fileName + " is uploaded by: " + revision.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import fr.free.nrw.commons.mwapi.Revision;
|
||||
|
||||
public class ReviewPagerAdapter extends FragmentStatePagerAdapter {
|
||||
ReviewImageFragment[] reviewImageFragments;
|
||||
|
||||
|
||||
public ReviewPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
reviewImageFragments = new ReviewImageFragment[] {
|
||||
new ReviewImageFragment(),
|
||||
new ReviewImageFragment(),
|
||||
new ReviewImageFragment(),
|
||||
new ReviewImageFragment()
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return reviewImageFragments.length;
|
||||
}
|
||||
|
||||
public void updateFileInformation(String fileName, Revision revision) {
|
||||
for (int i = 0; i < getCount(); i++) {
|
||||
ReviewImageFragment fragment = reviewImageFragments[i];
|
||||
fragment.update(i, fileName, revision);
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
public class ReviewViewPager extends ViewPager {
|
||||
|
||||
public ReviewViewPager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ReviewViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
// Never allow swiping to switch between pages
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Never allow swiping to switch between pages
|
||||
return false;
|
||||
}
|
||||
}
|
||||
138
app/src/main/java/fr/free/nrw/commons/review/SendThankTask.java
Normal file
138
app/src/main/java/fr/free/nrw/commons/review/SendThankTask.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package fr.free.nrw.commons.review;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
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 fr.free.nrw.commons.mwapi.Revision;
|
||||
import timber.log.Timber;
|
||||
|
||||
// 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<Void, Integer, Boolean> {
|
||||
|
||||
@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 Revision revision;
|
||||
|
||||
public SendThankTask(Context context, Media media, Revision 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.revisionId);
|
||||
|
||||
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(NotificationCompat.DEFAULT_ALL)
|
||||
.setContentTitle(title)
|
||||
.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(message))
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setProgress(0,0,false)
|
||||
.setOngoing(false)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||
notificationManager.notify(NOTIFICATION_SEND_THANK, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,8 @@ import fr.free.nrw.commons.contributions.MainActivity;
|
|||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.logging.CommonsLogSender;
|
||||
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;
|
||||
|
||||
|
|
@ -227,6 +229,11 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
|||
drawerLayout.closeDrawer(navigationView);
|
||||
BookmarksActivity.startYourself(this);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<String> extractCategories(String source) {
|
||||
ArrayList<String> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
9
app/src/main/res/drawable/ic_check_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_check_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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:fillColor="#FF000000"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_refresh_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_refresh_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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:fillColor="#FF000000"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/tab_indicator_default.xml
Normal file
12
app/src/main/res/drawable/tab_indicator_default.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="2dp"
|
||||
android:useLevel="false">
|
||||
<solid android:color="@android:color/darker_gray"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
8
app/src/main/res/drawable/tab_indicator_selected.xml
Normal file
8
app/src/main/res/drawable/tab_indicator_selected.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="false"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/commons_app_blue_dark"/>
|
||||
</shape>
|
||||
8
app/src/main/res/drawable/tab_selector.xml
Normal file
8
app/src/main/res/drawable/tab_selector.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:drawable="@drawable/tab_indicator_selected"
|
||||
android:state_selected="true"/>
|
||||
|
||||
<item android:drawable="@drawable/tab_indicator_default"/>
|
||||
</selector>
|
||||
63
app/src/main/res/layout/activity_review.xml
Normal file
63
app/src/main/res/layout/activity_review.xml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:orientation="vertical"
|
||||
android:weightSum="15">
|
||||
|
||||
<Button
|
||||
android:id="@+id/skip_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:text="SKIP THIS IMAGE"
|
||||
android:textColor="@color/button_blue_dark"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_weight="1"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
|
||||
<fr.free.nrw.commons.review.ReviewViewPager
|
||||
android:id="@+id/reviewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:fadingEdge="none"
|
||||
android:layout_weight="13.5"/>
|
||||
|
||||
|
||||
<com.viewpagerindicator.CirclePageIndicator
|
||||
android:id="@+id/reviewPagerIndicator"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:foregroundGravity="center_vertical"
|
||||
android:elevation="1dp"
|
||||
android:background="?attr/colorPrimaryDark"
|
||||
android:layout_weight="0.5"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<include layout="@layout/drawer_view" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
131
app/src/main/res/layout/fragment_review_image.xml
Normal file
131
app/src/main/res/layout/fragment_review_image.xml
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scroll"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/bottomview"
|
||||
android:layout_alignParentTop="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp">
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="0dp"
|
||||
android:src="@drawable/commons_logo_large" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/uploadOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center|bottom"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="#77000000"
|
||||
android:padding="@dimen/tiny_gap"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/imageCaption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFFFF"
|
||||
style="?android:textAppearanceMedium"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="visible"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/reviewQuestion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="32sp"
|
||||
android:textColor="?attr/reviewHeading"
|
||||
android:gravity="center_vertical"
|
||||
android:text="testing1"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/reviewQuestionContext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="22sp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="testing2"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:weightSum="2"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/yesButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="@dimen/activity_margin_horizontal"
|
||||
android:background="@android:color/transparent"
|
||||
android:text="@string/yes"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/yes_button_color"
|
||||
android:textAlignment="center"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/noButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="@dimen/activity_margin_horizontal"
|
||||
android:text="@string/no"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/no_button_color"
|
||||
android:textAlignment="center"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/bottomview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="10dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="?attr/colorPrimaryDark"></View>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
@ -22,6 +22,11 @@
|
|||
android:icon="@drawable/ic_round_star_filled_24px"
|
||||
android:title="@string/navigation_item_bookmarks"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_review"
|
||||
android:icon="@drawable/ic_check_black_24dp"
|
||||
android:title="@string/navigation_item_review"/>
|
||||
|
||||
</group>
|
||||
<group android:id="@+id/drawer_account">
|
||||
<item
|
||||
|
|
|
|||
10
app/src/main/res/menu/review_randomizer_menu.xml
Normal file
10
app/src/main/res/menu/review_randomizer_menu.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/action_review_randomizer"
|
||||
android:title="@string/refresh_button"
|
||||
app:showAsAction="ifRoom|withText"
|
||||
android:icon="@drawable/ic_refresh_white_24dp"
|
||||
/>
|
||||
|
||||
</menu>
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
<attr name="textEnabled" format="reference"/>
|
||||
<attr name="bookmarkButtonColor" format="reference"/>
|
||||
<attr name="rowButtonColor" format="reference"/>
|
||||
<attr name="reviewHeading" format="reference"/>
|
||||
|
||||
<attr name="contributionsListBackground" format="reference"/>
|
||||
<attr name="achievementBackground" format="reference"/>
|
||||
|
|
|
|||
|
|
@ -67,4 +67,6 @@
|
|||
<color name="black">#000000</color>
|
||||
|
||||
<color name="swipe_red" tools:ignore="MissingDefaultResource">#FF0000</color>
|
||||
<color name="yes_button_color">#B22222</color>
|
||||
<color name="no_button_color">#006400</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
<string name="title_activity_signup">Sign Up</string>
|
||||
<string name="title_activity_featured_images">Featured Images</string>
|
||||
<string name="title_activity_category_details">Category</string>
|
||||
<string name="title_activity_review">Peer Review</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="about_license">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. </string>
|
||||
<string name="trademarked_name" translatable="false">Wikimedia Commons</string>
|
||||
|
|
@ -229,6 +230,7 @@
|
|||
<string name="navigation_item_info">Tutorial</string>
|
||||
<string name="navigation_item_notification">Notifications</string>
|
||||
<string name="navigation_item_featured_images">Featured</string>
|
||||
<string name="navigation_item_review">Review</string>
|
||||
<string name="nearby_needs_permissions">Nearby places cannot be displayed without location permissions</string>
|
||||
<string name="no_description_found">no description found</string>
|
||||
<string name="nearby_info_menu_commons_article">Commons file page</string>
|
||||
|
|
@ -259,6 +261,8 @@
|
|||
<string name="nominate_deletion">Nominate for Deletion</string>
|
||||
<string name="nominated_for_deletion">This image has been nominated for deletion.</string>
|
||||
<string name="nominated_see_more"><![CDATA[<u>See webpage for details</u>]]></string>
|
||||
<string name="nominating_file_for_deletion">Nominating %1$s for deletion.</string>
|
||||
<string name="nominating_for_deletion_status">Nominating file for deletion: %1$s</string>
|
||||
<string name="view_browser">View in Browser</string>
|
||||
<string name="skip_login">Skip</string>
|
||||
<string name="navigation_item_login">Log in</string>
|
||||
|
|
@ -289,6 +293,8 @@
|
|||
<string name="no_internet">Internet unavailable</string>
|
||||
<string name="internet_established">Internet available</string>
|
||||
<string name="error_notifications">Error fetching notifications</string>
|
||||
<string name="error_review">Error fetching image for review. Press refresh to try again.</string>
|
||||
<string name="error_review_categories">Error fetching image categories for review. Press refresh to try again.</string>
|
||||
<string name="no_notifications">No notifications found</string>
|
||||
<string name="about_translate"><![CDATA[<u>Translate</u>]]></string>
|
||||
<string name="about_translate_title">Languages</string>
|
||||
|
|
@ -456,6 +462,50 @@ Upload your first media by tapping on the add button.</string>
|
|||
<string name="this_function_needs_network_connection">This function requires network connection, please check your connection settings.</string>
|
||||
<string name="bad_token_error_proposed_solution">Upload failed due to issues with edit token. Please try logging out and in again. </string>
|
||||
<string name="error_processing_image">Error occurred while processing the image. Please try again!</string>
|
||||
<string name="getting_edit_token">Getting token for editing</string>
|
||||
<string name="check_category_adding_template">Adding template for category check</string>
|
||||
<string name="check_category_notification_title">Requesting category check for %1$s</string>
|
||||
<string name="check_category_edit_summary">Requesting category check</string>
|
||||
<string name="check_category_success_title">Requesting category check: Success</string>
|
||||
<string name="check_category_failure_title">Requesting category check: Failed</string>
|
||||
<string name="check_category_success_message">Successfully requested category check for %1$s</string>
|
||||
<string name="check_category_failure_message">Could not request category check for %1$s</string>
|
||||
<string name="check_category_toast">Requesting category check for %1$s</string>
|
||||
<string name="nominate_for_deletion_edit_file_page">Adding delete message to file</string>
|
||||
<string name="nominate_for_deletion_done">Done</string>
|
||||
<string name="nominate_for_deletion_notify_user">Notifying User on Talk page</string>
|
||||
<string name="nominate_for_deletion_edit_deletion_request_log">Adding file to Delete requests log</string>
|
||||
<string name="nominate_for_deletion_create_deletion_request">Creating Delete requests subpage</string>
|
||||
<string name="notsure">Not sure</string>
|
||||
<string name="send_thank_success_title">Sending Thanks: Success</string>
|
||||
<string name="send_thank_success_message">Successfully sent thanks to %1$s</string>
|
||||
<string name="send_thank_failure_message">Failed to send thanks %1$s</string>
|
||||
<string name="send_thank_failure_title">Sending Thanks: Failure</string>
|
||||
<string name="send_thank_send">Sending thanks</string>
|
||||
<string name="send_thank_notification_title">Sending thanks</string>
|
||||
<string name="send_thank_toast">Sending Thanks for %1$s</string>
|
||||
|
||||
<string name="review_copyright">Does this follow the rules of copyright?</string>
|
||||
<string name="review_category">Is this correctly categorized?</string>
|
||||
<string name="review_spam">Is this in-scope?</string>
|
||||
<string name="review_thanks">Would you like to thank the contributor?</string>
|
||||
<string name="review_spam_explanation">Click NO to nominate this image for deletion if it is not useful at all.</string>
|
||||
<string name="review_copyright_explanation">Logos, screenshots, movie posters are often copyright violations.\n Click NO to nominate this image for deletion</string>
|
||||
<string name="review_thanks_explanation">%1$s will be encouraged by your appreciation</string>
|
||||
<string name="review_no_category">Oh, this is not even categorized!</string>
|
||||
<string name="review_category_explanation">This image is under %1$s categories.</string>
|
||||
<string name="review_spam_report_question">It is out of scope because it is</string>
|
||||
<string name="review_spam_report_problem">spam</string>
|
||||
<string name="review_c_violation_report_question">It is copyright violation because it is </string>
|
||||
<string name="review_c_violation_report_problem">copyRightViolation</string>
|
||||
<string name="review_category_yes_button_text">NO, MIS-CATEGORIZED</string>
|
||||
<string name="review_category_no_button_text">SEEMS FINE</string>
|
||||
<string name="review_spam_yes_button_text">NO, OUT OF SCOPE</string>
|
||||
<string name="review_spam_no_button_text">SEEMS FINE</string>
|
||||
<string name="review_copyright_yes_button_text">NO, COPYRIGHT VIOLATION</string>
|
||||
<string name="review_copyright_no_button_text">SEEMS FINE</string>
|
||||
<string name="review_thanks_yes_button_text">YES, WHY NOT</string>
|
||||
<string name="review_thanks_no_button_text">NEXT IMAGE</string>
|
||||
|
||||
<plurals name="receiving_shared_content">
|
||||
<item quantity="one">Receiving shared content. Processing the image might take some time depending on the size of the image and your device</item>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<item name="colorButtonNormal">@color/primaryColor</item>
|
||||
<item name="bookmarkButtonColor">@color/button_blue_dark</item>
|
||||
<item name="rowButtonColor">@color/button_blue_dark</item>
|
||||
<item name="reviewHeading">@color/white</item>
|
||||
|
||||
<item name="semitransparentText">@color/commons_app_blue_dark</item>
|
||||
<item name="subBackground">@color/sub_background_dark</item>
|
||||
|
|
@ -50,6 +51,7 @@
|
|||
<item name="colorButtonNormal">@color/primaryColor</item>
|
||||
<item name="bookmarkButtonColor">@color/button_blue</item>
|
||||
<item name="rowButtonColor">@color/button_blue</item>
|
||||
<item name="reviewHeading">@color/black</item>
|
||||
|
||||
<item name="semitransparentText">@color/commons_app_blue_light</item>
|
||||
<item name="subBackground">@color/sub_background_light</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue