mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Migration of review module from Java to Kotlin (#5950)
* Rename .java to .kt * Migrated repository module to Kotlin * Rename .java to .kt * Migrated review module to Kotlin
This commit is contained in:
parent
e070c5dbe8
commit
bafae821e2
15 changed files with 906 additions and 932 deletions
|
|
@ -232,7 +232,7 @@ public class MoreBottomSheetFragment extends BottomSheetDialogFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPeerReviewClicked() {
|
protected void onPeerReviewClicked() {
|
||||||
ReviewActivity.startYourself(getActivity(), getString(R.string.title_activity_review));
|
ReviewActivity.Companion.startYourself(getActivity(), getString(R.string.title_activity_review));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,334 +0,0 @@
|
||||||
package fr.free.nrw.commons.review;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
|
||||||
import fr.free.nrw.commons.databinding.ActivityReviewBinding;
|
|
||||||
import fr.free.nrw.commons.delete.DeleteHelper;
|
|
||||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import java.util.Locale;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class ReviewActivity extends BaseActivity {
|
|
||||||
|
|
||||||
|
|
||||||
private ActivityReviewBinding binding;
|
|
||||||
|
|
||||||
MediaDetailFragment mediaDetailFragment;
|
|
||||||
public ReviewPagerAdapter reviewPagerAdapter;
|
|
||||||
public ReviewController reviewController;
|
|
||||||
@Inject
|
|
||||||
ReviewHelper reviewHelper;
|
|
||||||
@Inject
|
|
||||||
DeleteHelper deleteHelper;
|
|
||||||
/**
|
|
||||||
* Represent fragment for ReviewImage
|
|
||||||
* Use to call some methods of ReviewImage fragment
|
|
||||||
*/
|
|
||||||
private ReviewImageFragment reviewImageFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag to check whether there are any non-hidden categories in the File
|
|
||||||
*/
|
|
||||||
private boolean hasNonHiddenCategories = false;
|
|
||||||
|
|
||||||
final String SAVED_MEDIA = "saved_media";
|
|
||||||
private Media media;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
if (media != null) {
|
|
||||||
outState.putParcelable(SAVED_MEDIA, media);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
reviewActivity.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
|
||||||
reviewActivity.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
context.startActivity(reviewActivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
public Media getMedia() {
|
|
||||||
return media;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
binding = ActivityReviewBinding.inflate(getLayoutInflater());
|
|
||||||
setContentView(binding.getRoot());
|
|
||||||
|
|
||||||
setSupportActionBar(binding.toolbarBinding.toolbar);
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
reviewController = new ReviewController(deleteHelper, this);
|
|
||||||
|
|
||||||
reviewPagerAdapter = new ReviewPagerAdapter(getSupportFragmentManager());
|
|
||||||
binding.viewPagerReview.setAdapter(reviewPagerAdapter);
|
|
||||||
binding.pagerIndicatorReview.setViewPager(binding.viewPagerReview);
|
|
||||||
binding.pbReviewImage.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
Drawable d[]=binding.skipImage.getCompoundDrawablesRelative();
|
|
||||||
d[2].setColorFilter(getApplicationContext().getResources().getColor(R.color.button_blue), PorterDuff.Mode.SRC_IN);
|
|
||||||
|
|
||||||
if (savedInstanceState != null && savedInstanceState.getParcelable(SAVED_MEDIA) != null) {
|
|
||||||
updateImage(savedInstanceState.getParcelable(SAVED_MEDIA)); // Use existing media if we have one
|
|
||||||
setUpMediaDetailOnOrientation();
|
|
||||||
} else {
|
|
||||||
runRandomizer(); //Run randomizer whenever everything is ready so that a first random image will be added
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.skipImage.setOnClickListener(view -> {
|
|
||||||
reviewImageFragment = getInstanceOfReviewImageFragment();
|
|
||||||
reviewImageFragment.disableButtons();
|
|
||||||
runRandomizer();
|
|
||||||
});
|
|
||||||
|
|
||||||
binding.reviewImageView.setOnClickListener(view ->setUpMediaDetailFragment());
|
|
||||||
|
|
||||||
binding.skipImage.setOnTouchListener((view, event) -> {
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP && event.getRawX() >= (
|
|
||||||
binding.skipImage.getRight() - binding.skipImage
|
|
||||||
.getCompoundDrawables()[2].getBounds().width())) {
|
|
||||||
showSkipImageInfo();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSupportNavigateUp() {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
public boolean runRandomizer() {
|
|
||||||
hasNonHiddenCategories = false;
|
|
||||||
binding.pbReviewImage.setVisibility(View.VISIBLE);
|
|
||||||
binding.viewPagerReview.setCurrentItem(0);
|
|
||||||
// Finds non-hidden categories from Media instance
|
|
||||||
compositeDisposable.add(reviewHelper.getRandomMedia()
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(this::checkWhetherFileIsUsedInWikis));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether media is used or not in any Wiki Page
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private void checkWhetherFileIsUsedInWikis(final Media media) {
|
|
||||||
compositeDisposable.add(reviewHelper.checkFileUsage(media.getFilename())
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(result -> {
|
|
||||||
// result false indicates media is not used in any wiki
|
|
||||||
if (!result) {
|
|
||||||
// Finds non-hidden categories from Media instance
|
|
||||||
findNonHiddenCategories(media);
|
|
||||||
} else {
|
|
||||||
runRandomizer();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds non-hidden categories and updates current image
|
|
||||||
*/
|
|
||||||
private void findNonHiddenCategories(Media media) {
|
|
||||||
for(String key : media.getCategoriesHiddenStatus().keySet()) {
|
|
||||||
Boolean value = media.getCategoriesHiddenStatus().get(key);
|
|
||||||
// If non-hidden category is found then set hasNonHiddenCategories to true
|
|
||||||
// so that category review cannot be skipped
|
|
||||||
if(!value) {
|
|
||||||
hasNonHiddenCategories = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reviewImageFragment = getInstanceOfReviewImageFragment();
|
|
||||||
reviewImageFragment.disableButtons();
|
|
||||||
updateImage(media);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private void updateImage(Media media) {
|
|
||||||
reviewHelper.addViewedImagesToDB(media.getPageId());
|
|
||||||
this.media = media;
|
|
||||||
String fileName = media.getFilename();
|
|
||||||
if (fileName.length() == 0) {
|
|
||||||
ViewUtil.showShortSnackbar(binding.drawerLayout, R.string.error_review);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If The Media User and Current Session Username is same then Skip the Image
|
|
||||||
if (media.getUser() != null && media.getUser().equals(AccountUtil.getUserName(getApplicationContext()))) {
|
|
||||||
runRandomizer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.reviewImageView.setImageURI(media.getImageUrl());
|
|
||||||
|
|
||||||
reviewController.onImageRefreshed(media); //file name is updated
|
|
||||||
compositeDisposable.add(reviewHelper.getFirstRevisionOfFile(fileName)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(revision -> {
|
|
||||||
reviewController.firstRevision = revision;
|
|
||||||
reviewPagerAdapter.updateFileInformation();
|
|
||||||
@SuppressLint({"StringFormatInvalid", "LocalSuppress"}) String caption = String.format(getString(R.string.review_is_uploaded_by), fileName, revision.getUser());
|
|
||||||
binding.tvImageCaption.setText(caption);
|
|
||||||
binding.pbReviewImage.setVisibility(View.GONE);
|
|
||||||
reviewImageFragment = getInstanceOfReviewImageFragment();
|
|
||||||
reviewImageFragment.enableButtons();
|
|
||||||
}));
|
|
||||||
binding.viewPagerReview.setCurrentItem(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void swipeToNext() {
|
|
||||||
int nextPos = binding.viewPagerReview.getCurrentItem() + 1;
|
|
||||||
// If currently at category fragment, then check whether the media has any non-hidden category
|
|
||||||
if (nextPos <= 3) {
|
|
||||||
binding.viewPagerReview.setCurrentItem(nextPos);
|
|
||||||
if (nextPos == 2) {
|
|
||||||
// The media has no non-hidden category. Such media are already flagged by server-side bots, so no need to review manually.
|
|
||||||
if (!hasNonHiddenCategories) {
|
|
||||||
swipeToNext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
runRandomizer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
compositeDisposable.clear();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showSkipImageInfo(){
|
|
||||||
DialogUtil.showAlertDialog(ReviewActivity.this,
|
|
||||||
getString(R.string.skip_image).toUpperCase(Locale.ROOT),
|
|
||||||
getString(R.string.skip_image_explanation),
|
|
||||||
getString(android.R.string.ok),
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showReviewImageInfo() {
|
|
||||||
DialogUtil.showAlertDialog(ReviewActivity.this,
|
|
||||||
getString(R.string.title_activity_review),
|
|
||||||
getString(R.string.review_image_explanation),
|
|
||||||
getString(android.R.string.ok),
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
MenuInflater inflater = getMenuInflater();
|
|
||||||
inflater.inflate(R.menu.menu_review_activty, menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.menu_image_info:
|
|
||||||
showReviewImageInfo();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this function return the instance of reviewImageFragment
|
|
||||||
*/
|
|
||||||
public ReviewImageFragment getInstanceOfReviewImageFragment(){
|
|
||||||
int currentItemOfReviewPager = binding.viewPagerReview.getCurrentItem();
|
|
||||||
reviewImageFragment = (ReviewImageFragment) reviewPagerAdapter.instantiateItem(binding.viewPagerReview, currentItemOfReviewPager);
|
|
||||||
return reviewImageFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set up the media detail fragment when click on the review image
|
|
||||||
*/
|
|
||||||
private void setUpMediaDetailFragment() {
|
|
||||||
if (binding.mediaDetailContainer.getVisibility() == View.GONE && media != null) {
|
|
||||||
binding.mediaDetailContainer.setVisibility(View.VISIBLE);
|
|
||||||
binding.reviewActivityContainer.setVisibility(View.INVISIBLE);
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
mediaDetailFragment = new MediaDetailFragment();
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putParcelable("media", media);
|
|
||||||
mediaDetailFragment.setArguments(bundle);
|
|
||||||
fragmentManager.beginTransaction().add(R.id.mediaDetailContainer, mediaDetailFragment).
|
|
||||||
addToBackStack("MediaDetail").commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* handle the back pressed event of this activity
|
|
||||||
* this function call every time when back button is pressed
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
if (binding.mediaDetailContainer.getVisibility() == View.VISIBLE) {
|
|
||||||
binding.mediaDetailContainer.setVisibility(View.GONE);
|
|
||||||
binding.reviewActivityContainer.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set up media detail fragment after orientation change
|
|
||||||
*/
|
|
||||||
private void setUpMediaDetailOnOrientation() {
|
|
||||||
Fragment mediaDetailFragment = getSupportFragmentManager()
|
|
||||||
.findFragmentById(R.id.mediaDetailContainer);
|
|
||||||
if (mediaDetailFragment != null) {
|
|
||||||
binding.mediaDetailContainer.setVisibility(View.VISIBLE);
|
|
||||||
binding.reviewActivityContainer.setVisibility(View.INVISIBLE);
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.replace(R.id.mediaDetailContainer, mediaDetailFragment).commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
336
app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt
Normal file
336
app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt
Normal file
|
|
@ -0,0 +1,336 @@
|
||||||
|
package fr.free.nrw.commons.review
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.auth.AccountUtil
|
||||||
|
import fr.free.nrw.commons.databinding.ActivityReviewBinding
|
||||||
|
import fr.free.nrw.commons.delete.DeleteHelper
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailFragment
|
||||||
|
import fr.free.nrw.commons.theme.BaseActivity
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ReviewActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityReviewBinding
|
||||||
|
|
||||||
|
private var mediaDetailFragment: MediaDetailFragment? = null
|
||||||
|
lateinit var reviewPagerAdapter: ReviewPagerAdapter
|
||||||
|
lateinit var reviewController: ReviewController
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var reviewHelper: ReviewHelper
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var deleteHelper: DeleteHelper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent fragment for ReviewImage
|
||||||
|
* Use to call some methods of ReviewImage fragment
|
||||||
|
*/
|
||||||
|
private var reviewImageFragment: ReviewImageFragment? = null
|
||||||
|
private var hasNonHiddenCategories = false
|
||||||
|
var media: Media? = null
|
||||||
|
|
||||||
|
private val SAVED_MEDIA = "saved_media"
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
media?.let {
|
||||||
|
outState.putParcelable(SAVED_MEDIA, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumers should be simply using this method to use this activity.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param title Page title
|
||||||
|
*/
|
||||||
|
companion object {
|
||||||
|
fun startYourself(context: Context, title: String) {
|
||||||
|
val reviewActivity = Intent(context, ReviewActivity::class.java)
|
||||||
|
reviewActivity.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||||
|
reviewActivity.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
context.startActivity(reviewActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityReviewBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
setSupportActionBar(binding.toolbarBinding?.toolbar)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
reviewController = ReviewController(deleteHelper, this)
|
||||||
|
|
||||||
|
reviewPagerAdapter = ReviewPagerAdapter(supportFragmentManager)
|
||||||
|
binding.viewPagerReview.adapter = reviewPagerAdapter
|
||||||
|
binding.pagerIndicatorReview.setViewPager(binding.viewPagerReview)
|
||||||
|
binding.pbReviewImage.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
binding.skipImage.compoundDrawablesRelative[2]?.setColorFilter(
|
||||||
|
resources.getColor(R.color.button_blue),
|
||||||
|
PorterDuff.Mode.SRC_IN
|
||||||
|
)
|
||||||
|
|
||||||
|
if (savedInstanceState?.getParcelable<Media>(SAVED_MEDIA) != null) {
|
||||||
|
updateImage(savedInstanceState.getParcelable(SAVED_MEDIA)!!)
|
||||||
|
setUpMediaDetailOnOrientation()
|
||||||
|
} else {
|
||||||
|
runRandomizer()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.skipImage.setOnClickListener {
|
||||||
|
reviewImageFragment = getInstanceOfReviewImageFragment()
|
||||||
|
reviewImageFragment?.disableButtons()
|
||||||
|
runRandomizer()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.reviewImageView.setOnClickListener {
|
||||||
|
setUpMediaDetailFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.skipImage.setOnTouchListener { _, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_UP &&
|
||||||
|
event.rawX >= (binding.skipImage.right - binding.skipImage.compoundDrawables[2].bounds.width())
|
||||||
|
) {
|
||||||
|
showSkipImageInfo()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
fun runRandomizer(): Boolean {
|
||||||
|
hasNonHiddenCategories = false
|
||||||
|
binding.pbReviewImage.visibility = View.VISIBLE
|
||||||
|
binding.viewPagerReview.currentItem = 0
|
||||||
|
|
||||||
|
compositeDisposable.add(
|
||||||
|
reviewHelper.getRandomMedia()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(::checkWhetherFileIsUsedInWikis)
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether media is used or not in any Wiki Page
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private fun checkWhetherFileIsUsedInWikis(media: Media) {
|
||||||
|
compositeDisposable.add(
|
||||||
|
reviewHelper.checkFileUsage(media.filename)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { result ->
|
||||||
|
if (!result) {
|
||||||
|
findNonHiddenCategories(media)
|
||||||
|
} else {
|
||||||
|
runRandomizer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds non-hidden categories and updates current image
|
||||||
|
*/
|
||||||
|
private fun findNonHiddenCategories(media: Media) {
|
||||||
|
this.media = media
|
||||||
|
// If non-hidden category is found then set hasNonHiddenCategories to true
|
||||||
|
// so that category review cannot be skipped
|
||||||
|
hasNonHiddenCategories = media.categoriesHiddenStatus.values.any { !it }
|
||||||
|
reviewImageFragment = getInstanceOfReviewImageFragment()
|
||||||
|
reviewImageFragment?.disableButtons()
|
||||||
|
updateImage(media)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private fun updateImage(media: Media) {
|
||||||
|
reviewHelper.addViewedImagesToDB(media.pageId)
|
||||||
|
this.media = media
|
||||||
|
val fileName = media.filename
|
||||||
|
|
||||||
|
if (fileName.isNullOrEmpty()) {
|
||||||
|
ViewUtil.showShortSnackbar(binding.drawerLayout, R.string.error_review)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//If The Media User and Current Session Username is same then Skip the Image
|
||||||
|
if (media.user == AccountUtil.getUserName(applicationContext)) {
|
||||||
|
runRandomizer()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.reviewImageView.setImageURI(media.imageUrl)
|
||||||
|
|
||||||
|
reviewController.onImageRefreshed(media) // filename is updated
|
||||||
|
compositeDisposable.add(
|
||||||
|
reviewHelper.getFirstRevisionOfFile(fileName)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { revision ->
|
||||||
|
reviewController.firstRevision = revision
|
||||||
|
reviewPagerAdapter.updateFileInformation()
|
||||||
|
val caption = getString(
|
||||||
|
R.string.review_is_uploaded_by,
|
||||||
|
fileName,
|
||||||
|
revision.user
|
||||||
|
)
|
||||||
|
binding.tvImageCaption.text = caption
|
||||||
|
binding.pbReviewImage.visibility = View.GONE
|
||||||
|
reviewImageFragment = getInstanceOfReviewImageFragment()
|
||||||
|
reviewImageFragment?.enableButtons()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
binding.viewPagerReview.currentItem = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun swipeToNext() {
|
||||||
|
val nextPos = binding.viewPagerReview.currentItem + 1
|
||||||
|
|
||||||
|
// If currently at category fragment, then check whether the media has any non-hidden category
|
||||||
|
if (nextPos <= 3) {
|
||||||
|
binding.viewPagerReview.currentItem = nextPos
|
||||||
|
if (nextPos == 2 && !hasNonHiddenCategories)
|
||||||
|
{
|
||||||
|
// The media has no non-hidden category. Such media are already flagged by server-side bots, so no need to review manually.
|
||||||
|
swipeToNext()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
runRandomizer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
compositeDisposable.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showSkipImageInfo() {
|
||||||
|
DialogUtil.showAlertDialog(
|
||||||
|
this,
|
||||||
|
getString(R.string.skip_image).uppercase(Locale.ROOT),
|
||||||
|
getString(R.string.skip_image_explanation),
|
||||||
|
getString(android.R.string.ok),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showReviewImageInfo() {
|
||||||
|
DialogUtil.showAlertDialog(
|
||||||
|
this,
|
||||||
|
getString(R.string.title_activity_review),
|
||||||
|
getString(R.string.review_image_explanation),
|
||||||
|
getString(android.R.string.ok),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.menu_review_activty, menu)
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.menu_image_info -> {
|
||||||
|
showReviewImageInfo()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function return the instance of reviewImageFragment
|
||||||
|
*/
|
||||||
|
private fun getInstanceOfReviewImageFragment(): ReviewImageFragment? {
|
||||||
|
val currentItemOfReviewPager = binding.viewPagerReview.currentItem
|
||||||
|
return reviewPagerAdapter.instantiateItem(
|
||||||
|
binding.viewPagerReview,
|
||||||
|
currentItemOfReviewPager
|
||||||
|
) as? ReviewImageFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set up the media detail fragment when click on the review image
|
||||||
|
*/
|
||||||
|
private fun setUpMediaDetailFragment() {
|
||||||
|
if (binding.mediaDetailContainer.visibility == View.GONE && media != null) {
|
||||||
|
binding.mediaDetailContainer.visibility = View.VISIBLE
|
||||||
|
binding.reviewActivityContainer.visibility = View.INVISIBLE
|
||||||
|
val fragmentManager = supportFragmentManager
|
||||||
|
mediaDetailFragment = MediaDetailFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putParcelable("media", media)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.add(R.id.mediaDetailContainer, mediaDetailFragment!!)
|
||||||
|
.addToBackStack("MediaDetail")
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handle the back pressed event of this activity
|
||||||
|
* this function call every time when back button is pressed
|
||||||
|
*/
|
||||||
|
@Deprecated("This method has been deprecated in favor of using the" +
|
||||||
|
"{@link OnBackPressedDispatcher} via {@link #getOnBackPressedDispatcher()}." +
|
||||||
|
"The OnBackPressedDispatcher controls how back button events are dispatched" +
|
||||||
|
"to one or more {@link OnBackPressedCallback} objects.")
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (binding.mediaDetailContainer.visibility == View.VISIBLE) {
|
||||||
|
binding.mediaDetailContainer.visibility = View.GONE
|
||||||
|
binding.reviewActivityContainer.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set up media detail fragment after orientation change
|
||||||
|
*/
|
||||||
|
private fun setUpMediaDetailOnOrientation() {
|
||||||
|
val fragment = supportFragmentManager.findFragmentById(R.id.mediaDetailContainer)
|
||||||
|
fragment?.let {
|
||||||
|
binding.mediaDetailContainer.visibility = View.VISIBLE
|
||||||
|
binding.reviewActivityContainer.visibility = View.INVISIBLE
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.mediaDetailContainer, it)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
package fr.free.nrw.commons.review;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
|
||||||
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
|
|
||||||
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.actions.PageEditClient;
|
|
||||||
import fr.free.nrw.commons.actions.ThanksClient;
|
|
||||||
import fr.free.nrw.commons.delete.DeleteHelper;
|
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.ObservableSource;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class ReviewController {
|
|
||||||
private static final int NOTIFICATION_SEND_THANK = 0x102;
|
|
||||||
private static final int NOTIFICATION_CHECK_CATEGORY = 0x101;
|
|
||||||
protected static ArrayList<String> categories;
|
|
||||||
@Inject
|
|
||||||
ThanksClient thanksClient;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SessionManager sessionManager;
|
|
||||||
private final DeleteHelper deleteHelper;
|
|
||||||
@Nullable
|
|
||||||
MwQueryPage.Revision firstRevision; // TODO: maybe we can expand this class to include fileName
|
|
||||||
@Inject
|
|
||||||
@Named("commons-page-edit")
|
|
||||||
PageEditClient pageEditClient;
|
|
||||||
private NotificationManager notificationManager;
|
|
||||||
private NotificationCompat.Builder notificationBuilder;
|
|
||||||
private Media media;
|
|
||||||
|
|
||||||
ReviewController(DeleteHelper deleteHelper, Context context) {
|
|
||||||
this.deleteHelper = deleteHelper;
|
|
||||||
CommonsApplication.createNotificationChannel(context.getApplicationContext());
|
|
||||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
notificationBuilder = new NotificationCompat.Builder(context, CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onImageRefreshed(Media media) {
|
|
||||||
this.media = media;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Media getMedia() {
|
|
||||||
return media;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DeleteReason {
|
|
||||||
SPAM,
|
|
||||||
COPYRIGHT_VIOLATION
|
|
||||||
}
|
|
||||||
|
|
||||||
void reportSpam(@NonNull Activity activity, ReviewCallback reviewCallback) {
|
|
||||||
Timber.d("Report spam for %s", media.getFilename());
|
|
||||||
deleteHelper.askReasonAndExecute(media,
|
|
||||||
activity,
|
|
||||||
activity.getResources().getString(R.string.review_spam_report_question),
|
|
||||||
DeleteReason.SPAM,
|
|
||||||
reviewCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reportPossibleCopyRightViolation(@NonNull Activity activity, ReviewCallback reviewCallback) {
|
|
||||||
Timber.d("Report spam for %s", media.getFilename());
|
|
||||||
deleteHelper.askReasonAndExecute(media,
|
|
||||||
activity,
|
|
||||||
activity.getResources().getString(R.string.review_c_violation_report_question),
|
|
||||||
DeleteReason.COPYRIGHT_VIOLATION,
|
|
||||||
reviewCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
void reportWrongCategory(@NonNull Activity activity, ReviewCallback reviewCallback) {
|
|
||||||
Context context = activity.getApplicationContext();
|
|
||||||
ApplicationlessInjection
|
|
||||||
.getInstance(context)
|
|
||||||
.getCommonsApplicationComponent()
|
|
||||||
.inject(this);
|
|
||||||
|
|
||||||
ViewUtil.showShortToast(context, context.getString(R.string.check_category_toast, media.getDisplayTitle()));
|
|
||||||
|
|
||||||
publishProgress(context, 0);
|
|
||||||
String summary = context.getString(R.string.check_category_edit_summary);
|
|
||||||
Observable.defer((Callable<ObservableSource<Boolean>>) () ->
|
|
||||||
pageEditClient.appendEdit(media.getFilename(), "\n{{subst:chc}}\n", summary))
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe((result) -> {
|
|
||||||
publishProgress(context, 2);
|
|
||||||
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());
|
|
||||||
reviewCallback.onSuccess();
|
|
||||||
} else {
|
|
||||||
title = context.getString(R.string.check_category_failure_title);
|
|
||||||
message = context.getString(R.string.check_category_failure_message, media.getDisplayTitle());
|
|
||||||
reviewCallback.onFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
showNotification(title, message);
|
|
||||||
|
|
||||||
}, Timber::e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publishProgress(@NonNull Context context, int i) {
|
|
||||||
int[] messages = new int[]{R.string.getting_edit_token, R.string.check_category_adding_template};
|
|
||||||
String message = "";
|
|
||||||
if (0 < i && i < messages.length) {
|
|
||||||
message = context.getString(messages[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, i, false)
|
|
||||||
.setOngoing(true);
|
|
||||||
notificationManager.notify(NOTIFICATION_CHECK_CATEGORY, notificationBuilder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint({"CheckResult", "StringFormatInvalid"})
|
|
||||||
void sendThanks(@NonNull Activity activity) {
|
|
||||||
Context context = activity.getApplicationContext();
|
|
||||||
ApplicationlessInjection
|
|
||||||
.getInstance(context)
|
|
||||||
.getCommonsApplicationComponent()
|
|
||||||
.inject(this);
|
|
||||||
ViewUtil.showShortToast(context, context.getString(R.string.send_thank_toast, media.getDisplayTitle()));
|
|
||||||
|
|
||||||
if (firstRevision == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Observable.defer((Callable<ObservableSource<Boolean>>) () -> thanksClient.thank(firstRevision.getRevisionId()))
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(result -> {
|
|
||||||
displayThanksToast(context, result);
|
|
||||||
}, throwable -> {
|
|
||||||
if (throwable instanceof InvalidLoginTokenException) {
|
|
||||||
final String username = sessionManager.getUserName();
|
|
||||||
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
|
|
||||||
activity,
|
|
||||||
activity.getString(R.string.invalid_login_message),
|
|
||||||
username
|
|
||||||
);
|
|
||||||
|
|
||||||
CommonsApplication.getInstance().clearApplicationData(
|
|
||||||
activity, logoutListener);
|
|
||||||
} else {
|
|
||||||
Timber.e(throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid")
|
|
||||||
private void displayThanksToast(final Context context, final boolean result){
|
|
||||||
final String message;
|
|
||||||
final 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewUtil.showShortToast(context,message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showNotification(String title, String message) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ReviewCallback {
|
|
||||||
void onSuccess();
|
|
||||||
|
|
||||||
void onFailure();
|
|
||||||
|
|
||||||
void onTokenException(Exception e);
|
|
||||||
|
|
||||||
void disableButtons();
|
|
||||||
|
|
||||||
void enableButtons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
231
app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt
Normal file
231
app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
package fr.free.nrw.commons.review
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.auth.SessionManager
|
||||||
|
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
|
||||||
|
import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage
|
||||||
|
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.concurrent.Callable
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Named
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.actions.PageEditClient
|
||||||
|
import fr.free.nrw.commons.actions.ThanksClient
|
||||||
|
import fr.free.nrw.commons.delete.DeleteHelper
|
||||||
|
import fr.free.nrw.commons.di.ApplicationlessInjection
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.ObservableSource
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class ReviewController @Inject constructor(
|
||||||
|
private val deleteHelper: DeleteHelper,
|
||||||
|
context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val NOTIFICATION_SEND_THANK = 0x102
|
||||||
|
private const val NOTIFICATION_CHECK_CATEGORY = 0x101
|
||||||
|
protected var categories: ArrayList<String> = ArrayList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var thanksClient: ThanksClient
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@field: Named("commons-page-edit")
|
||||||
|
lateinit var pageEditClient: PageEditClient
|
||||||
|
|
||||||
|
var firstRevision: MwQueryPage.Revision? = null // TODO: maybe we can expand this class to include fileName
|
||||||
|
|
||||||
|
private val notificationManager: NotificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
private val notificationBuilder: NotificationCompat.Builder =
|
||||||
|
NotificationCompat.Builder(context, CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL)
|
||||||
|
|
||||||
|
var media: Media? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
CommonsApplication.createNotificationChannel(context.applicationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onImageRefreshed(media: Media) {
|
||||||
|
this.media = media
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DeleteReason {
|
||||||
|
SPAM,
|
||||||
|
COPYRIGHT_VIOLATION
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reportSpam(activity: Activity, reviewCallback: ReviewCallback) {
|
||||||
|
Timber.d("Report spam for %s", media?.filename)
|
||||||
|
deleteHelper.askReasonAndExecute(
|
||||||
|
media,
|
||||||
|
activity,
|
||||||
|
activity.resources.getString(R.string.review_spam_report_question),
|
||||||
|
DeleteReason.SPAM,
|
||||||
|
reviewCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reportPossibleCopyRightViolation(activity: Activity, reviewCallback: ReviewCallback) {
|
||||||
|
Timber.d("Report copyright violation for %s", media?.filename)
|
||||||
|
deleteHelper.askReasonAndExecute(
|
||||||
|
media,
|
||||||
|
activity,
|
||||||
|
activity.resources.getString(R.string.review_c_violation_report_question),
|
||||||
|
DeleteReason.COPYRIGHT_VIOLATION,
|
||||||
|
reviewCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
fun reportWrongCategory(activity: Activity, reviewCallback: ReviewCallback) {
|
||||||
|
val context = activity.applicationContext
|
||||||
|
ApplicationlessInjection
|
||||||
|
.getInstance(context)
|
||||||
|
.commonsApplicationComponent
|
||||||
|
.inject(this)
|
||||||
|
|
||||||
|
ViewUtil.showShortToast(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.check_category_toast, media?.displayTitle)
|
||||||
|
)
|
||||||
|
|
||||||
|
publishProgress(context, 0)
|
||||||
|
val summary = context.getString(R.string.check_category_edit_summary)
|
||||||
|
|
||||||
|
Observable.defer {
|
||||||
|
pageEditClient.appendEdit(media?.filename ?: "", "\n{{subst:chc}}\n", summary)
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ result ->
|
||||||
|
publishProgress(context, 2)
|
||||||
|
val (title, message) = if (result) {
|
||||||
|
reviewCallback.onSuccess()
|
||||||
|
context.getString(R.string.check_category_success_title) to
|
||||||
|
context.getString(R.string.check_category_success_message, media?.displayTitle)
|
||||||
|
} else {
|
||||||
|
reviewCallback.onFailure()
|
||||||
|
context.getString(R.string.check_category_failure_title) to
|
||||||
|
context.getString(R.string.check_category_failure_message, media?.displayTitle)
|
||||||
|
}
|
||||||
|
showNotification(title, message)
|
||||||
|
}, Timber::e)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun publishProgress(context: Context, progress: Int) {
|
||||||
|
val messages = arrayOf(
|
||||||
|
R.string.getting_edit_token,
|
||||||
|
R.string.check_category_adding_template
|
||||||
|
)
|
||||||
|
|
||||||
|
val message = if (progress in 1 until messages.size) {
|
||||||
|
context.getString(messages[progress])
|
||||||
|
} else ""
|
||||||
|
|
||||||
|
notificationBuilder.setContentTitle(
|
||||||
|
context.getString(
|
||||||
|
R.string.check_category_notification_title,
|
||||||
|
media?.displayTitle
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
||||||
|
.setSmallIcon(R.drawable.ic_launcher)
|
||||||
|
.setProgress(messages.size, progress, false)
|
||||||
|
.setOngoing(true)
|
||||||
|
|
||||||
|
notificationManager.notify(NOTIFICATION_CHECK_CATEGORY, notificationBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
fun sendThanks(activity: Activity) {
|
||||||
|
val context = activity.applicationContext
|
||||||
|
ApplicationlessInjection
|
||||||
|
.getInstance(context)
|
||||||
|
.commonsApplicationComponent
|
||||||
|
.inject(this)
|
||||||
|
|
||||||
|
ViewUtil.showShortToast(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.send_thank_toast, media?.displayTitle)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (firstRevision == null) return
|
||||||
|
|
||||||
|
Observable.defer {
|
||||||
|
thanksClient.thank(firstRevision!!.revisionId)
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ result ->
|
||||||
|
displayThanksToast(context, result)
|
||||||
|
}, { throwable ->
|
||||||
|
if (throwable is InvalidLoginTokenException) {
|
||||||
|
val username = sessionManager.userName
|
||||||
|
val logoutListener = CommonsApplication.BaseLogoutListener(
|
||||||
|
activity,
|
||||||
|
activity.getString(R.string.invalid_login_message),
|
||||||
|
username
|
||||||
|
)
|
||||||
|
CommonsApplication.instance.clearApplicationData(activity, logoutListener)
|
||||||
|
} else {
|
||||||
|
Timber.e(throwable)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StringFormatInvalid")
|
||||||
|
private fun displayThanksToast(context: Context, result: Boolean) {
|
||||||
|
val (title, message) = if (result) {
|
||||||
|
context.getString(R.string.send_thank_success_title) to
|
||||||
|
context.getString(R.string.send_thank_success_message, media?.displayTitle)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.send_thank_failure_title) to
|
||||||
|
context.getString(R.string.send_thank_failure_message, media?.displayTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewUtil.showShortToast(context, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNotification(title: String, message: String) {
|
||||||
|
notificationBuilder.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setStyle(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())
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReviewCallback {
|
||||||
|
fun onSuccess()
|
||||||
|
fun onFailure()
|
||||||
|
fun onTokenException(e: Exception)
|
||||||
|
fun disableButtons()
|
||||||
|
fun enableButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
package fr.free.nrw.commons.review;
|
package fr.free.nrw.commons.review
|
||||||
|
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy;
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query;
|
import androidx.room.Query
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dao interface for reviewed images database
|
* Dao interface for reviewed images database
|
||||||
*/
|
*/
|
||||||
@Dao
|
@Dao
|
||||||
public interface ReviewDao {
|
interface ReviewDao {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts reviewed/skipped image identifier into the database
|
* Inserts reviewed/skipped image identifier into the database
|
||||||
|
|
@ -17,7 +17,7 @@ public interface ReviewDao {
|
||||||
* @param reviewEntity
|
* @param reviewEntity
|
||||||
*/
|
*/
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
void insert(ReviewEntity reviewEntity);
|
fun insert(reviewEntity: ReviewEntity)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the image has already been reviewed/skipped by the user
|
* Checks if the image has already been reviewed/skipped by the user
|
||||||
|
|
@ -26,7 +26,6 @@ public interface ReviewDao {
|
||||||
* @param imageId
|
* @param imageId
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Query( "SELECT EXISTS (SELECT * from `reviewed-images` where imageId = (:imageId))")
|
@Query("SELECT EXISTS (SELECT * from `reviewed-images` where imageId = (:imageId))")
|
||||||
Boolean isReviewedAlready(String imageId);
|
fun isReviewedAlready(imageId: String): Boolean
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package fr.free.nrw.commons.review;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.room.Entity;
|
|
||||||
import androidx.room.PrimaryKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entity to store reviewed/skipped images identifier
|
|
||||||
*/
|
|
||||||
@Entity(tableName = "reviewed-images")
|
|
||||||
public class ReviewEntity {
|
|
||||||
@PrimaryKey
|
|
||||||
@NonNull
|
|
||||||
String imageId;
|
|
||||||
|
|
||||||
public ReviewEntity(String imageId) {
|
|
||||||
this.imageId = imageId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.kt
Normal file
13
app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.kt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package fr.free.nrw.commons.review
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity to store reviewed/skipped images identifier
|
||||||
|
*/
|
||||||
|
@Entity(tableName = "reviewed-images")
|
||||||
|
data class ReviewEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
val imageId: String
|
||||||
|
)
|
||||||
|
|
@ -77,7 +77,7 @@ class ReviewHelper
|
||||||
* @param image
|
* @param image
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun getReviewStatus(image: String?): Boolean = dao?.isReviewedAlready(image) ?: false
|
fun getReviewStatus(image: String?): Boolean = image?.let { dao?.isReviewedAlready(it) } ?: false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the first revision of the file from filename
|
* Gets the first revision of the file from filename
|
||||||
|
|
@ -132,7 +132,7 @@ class ReviewHelper
|
||||||
*/
|
*/
|
||||||
fun addViewedImagesToDB(imageId: String?) {
|
fun addViewedImagesToDB(imageId: String?) {
|
||||||
Completable
|
Completable
|
||||||
.fromAction { dao!!.insert(ReviewEntity(imageId)) }
|
.fromAction { imageId?.let { ReviewEntity(it) }?.let { dao!!.insert(it) } }
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
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 androidx.annotation.NonNull;
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.Media;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
|
||||||
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
|
|
||||||
import fr.free.nrw.commons.databinding.FragmentReviewImageBinding;
|
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
|
||||||
|
|
||||||
static final int CATEGORY = 2;
|
|
||||||
private static final int SPAM = 0;
|
|
||||||
private static final int COPYRIGHT = 1;
|
|
||||||
private static final int THANKS = 3;
|
|
||||||
|
|
||||||
private int position;
|
|
||||||
|
|
||||||
private FragmentReviewImageBinding binding;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SessionManager sessionManager;
|
|
||||||
|
|
||||||
|
|
||||||
// Constant variable used to store user's key name for onSaveInstanceState method
|
|
||||||
private final String SAVED_USER = "saved_user";
|
|
||||||
|
|
||||||
// Variable that stores the value of user
|
|
||||||
private String user;
|
|
||||||
|
|
||||||
public void update(final int position) {
|
|
||||||
this.position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String updateCategoriesQuestion() {
|
|
||||||
final Media media = getReviewActivity().getMedia();
|
|
||||||
if (media != null && media.getCategoriesHiddenStatus() != null && isAdded()) {
|
|
||||||
// Filter category name attribute from all categories
|
|
||||||
final List<String> categories = new ArrayList<>();
|
|
||||||
for(final String key : media.getCategoriesHiddenStatus().keySet()) {
|
|
||||||
String value = String.valueOf(key);
|
|
||||||
// Each category returned has a format like "Category:<some-category-name>"
|
|
||||||
// so remove the prefix "Category:"
|
|
||||||
final int index = key.indexOf("Category:");
|
|
||||||
if(index == 0) {
|
|
||||||
value = key.substring(9);
|
|
||||||
}
|
|
||||||
categories.add(value);
|
|
||||||
}
|
|
||||||
String catString = TextUtils.join(", ", categories);
|
|
||||||
if (catString != null && !catString.equals("") && binding.tvReviewQuestionContext != null) {
|
|
||||||
catString = "<b>" + catString + "</b>";
|
|
||||||
final String stringToConvertHtml = String.format(getResources().getString(R.string.review_category_explanation), catString);
|
|
||||||
return Html.fromHtml(stringToConvertHtml).toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getResources().getString(R.string.review_no_category);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
|
||||||
final Bundle savedInstanceState) {
|
|
||||||
position = getArguments().getInt("position");
|
|
||||||
binding = FragmentReviewImageBinding.inflate(inflater, container, false);
|
|
||||||
|
|
||||||
final String question;
|
|
||||||
String explanation=null;
|
|
||||||
String yesButtonText;
|
|
||||||
final String noButtonText;
|
|
||||||
|
|
||||||
binding.buttonYes.setOnClickListener(view -> onYesButtonClicked());
|
|
||||||
|
|
||||||
switch (position) {
|
|
||||||
case SPAM:
|
|
||||||
question = getString(R.string.review_spam);
|
|
||||||
explanation = getString(R.string.review_spam_explanation);
|
|
||||||
yesButtonText = getString(R.string.yes);
|
|
||||||
noButtonText = getString(R.string.no);
|
|
||||||
binding.buttonNo.setOnClickListener(view -> getReviewActivity()
|
|
||||||
.reviewController.reportSpam(requireActivity(), getReviewCallback()));
|
|
||||||
break;
|
|
||||||
case COPYRIGHT:
|
|
||||||
enableButtons();
|
|
||||||
question = getString(R.string.review_copyright);
|
|
||||||
explanation = getString(R.string.review_copyright_explanation);
|
|
||||||
yesButtonText = getString(R.string.yes);
|
|
||||||
noButtonText = getString(R.string.no);
|
|
||||||
binding.buttonNo.setOnClickListener(view -> getReviewActivity()
|
|
||||||
.reviewController
|
|
||||||
.reportPossibleCopyRightViolation(requireActivity(), getReviewCallback()));
|
|
||||||
break;
|
|
||||||
case CATEGORY:
|
|
||||||
enableButtons();
|
|
||||||
question = getString(R.string.review_category);
|
|
||||||
explanation = updateCategoriesQuestion();
|
|
||||||
yesButtonText = getString(R.string.yes);
|
|
||||||
noButtonText = getString(R.string.no);
|
|
||||||
binding.buttonNo.setOnClickListener(view -> {
|
|
||||||
getReviewActivity()
|
|
||||||
.reviewController
|
|
||||||
.reportWrongCategory(requireActivity(), getReviewCallback());
|
|
||||||
getReviewActivity().swipeToNext();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case THANKS:
|
|
||||||
enableButtons();
|
|
||||||
question = getString(R.string.review_thanks);
|
|
||||||
|
|
||||||
if (getReviewActivity().reviewController.firstRevision != null) {
|
|
||||||
user = getReviewActivity().reviewController.firstRevision.getUser();
|
|
||||||
} else {
|
|
||||||
if(savedInstanceState != null) {
|
|
||||||
user = savedInstanceState.getString(SAVED_USER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//if the user is null because of whatsoever reason, review will not be sent anyways
|
|
||||||
if (!TextUtils.isEmpty(user)) {
|
|
||||||
explanation = getString(R.string.review_thanks_explanation, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that the yes and no buttons are swapped in this section
|
|
||||||
yesButtonText = getString(R.string.review_thanks_yes_button_text);
|
|
||||||
noButtonText = getString(R.string.review_thanks_no_button_text);
|
|
||||||
binding.buttonYes.setTextColor(Color.parseColor("#116aaa"));
|
|
||||||
binding.buttonNo.setTextColor(Color.parseColor("#228b22"));
|
|
||||||
binding.buttonNo.setOnClickListener(view -> {
|
|
||||||
getReviewActivity().reviewController.sendThanks(getReviewActivity());
|
|
||||||
getReviewActivity().swipeToNext();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
enableButtons();
|
|
||||||
question = "How did we get here?";
|
|
||||||
explanation = "No idea.";
|
|
||||||
yesButtonText = "yes";
|
|
||||||
noButtonText = "no";
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.tvReviewQuestion.setText(question);
|
|
||||||
binding.tvReviewQuestionContext.setText(explanation);
|
|
||||||
binding.buttonYes.setText(yesButtonText);
|
|
||||||
binding.buttonNo.setText(noButtonText);
|
|
||||||
return binding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will be called when configuration changes happen
|
|
||||||
*
|
|
||||||
* @param outState
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
//Save user name when configuration changes happen
|
|
||||||
outState.putString(SAVED_USER, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReviewController.ReviewCallback getReviewCallback() {
|
|
||||||
return new ReviewController
|
|
||||||
.ReviewCallback() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
getReviewActivity().runRandomizer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure() {
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTokenException(final Exception e) {
|
|
||||||
if (e instanceof InvalidLoginTokenException){
|
|
||||||
final String username = sessionManager.getUserName();
|
|
||||||
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
|
|
||||||
getActivity(),
|
|
||||||
requireActivity().getString(R.string.invalid_login_message),
|
|
||||||
username
|
|
||||||
);
|
|
||||||
|
|
||||||
CommonsApplication.getInstance().clearApplicationData(
|
|
||||||
requireActivity(), logoutListener);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when an image is being loaded
|
|
||||||
* to disable the review buttons
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void disableButtons() {
|
|
||||||
ReviewImageFragment.this.disableButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when an image has
|
|
||||||
* been loaded to enable the review buttons.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void enableButtons() {
|
|
||||||
ReviewImageFragment.this.enableButtons();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when an image has
|
|
||||||
* been loaded to enable the review buttons.
|
|
||||||
*/
|
|
||||||
public void enableButtons() {
|
|
||||||
binding.buttonYes.setEnabled(true);
|
|
||||||
binding.buttonYes.setAlpha(1);
|
|
||||||
binding.buttonNo.setEnabled(true);
|
|
||||||
binding.buttonNo.setAlpha(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when an image is being loaded
|
|
||||||
* to disable the review buttons
|
|
||||||
*/
|
|
||||||
public void disableButtons() {
|
|
||||||
binding.buttonYes.setEnabled(false);
|
|
||||||
binding.buttonYes.setAlpha(0.5f);
|
|
||||||
binding.buttonNo.setEnabled(false);
|
|
||||||
binding.buttonNo.setAlpha(0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onYesButtonClicked() {
|
|
||||||
getReviewActivity().swipeToNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReviewActivity getReviewActivity() {
|
|
||||||
return (ReviewActivity) requireActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
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 fr.free.nrw.commons.CommonsApplication
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.auth.SessionManager
|
||||||
|
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
|
||||||
|
import fr.free.nrw.commons.databinding.FragmentReviewImageBinding
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||||
|
import java.util.ArrayList
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewImageFragment : CommonsDaggerSupportFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CATEGORY = 2
|
||||||
|
private const val SPAM = 0
|
||||||
|
private const val COPYRIGHT = 1
|
||||||
|
private const val THANKS = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
private var position: Int = 0
|
||||||
|
private var binding: FragmentReviewImageBinding? = null
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
// Constant variable used to store user's key name for onSaveInstanceState method
|
||||||
|
private val SAVED_USER = "saved_user"
|
||||||
|
|
||||||
|
// Variable that stores the value of user
|
||||||
|
private var user: String? = null
|
||||||
|
|
||||||
|
fun update(position: Int) {
|
||||||
|
this.position = position
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCategoriesQuestion(): String {
|
||||||
|
val media = reviewActivity.media
|
||||||
|
if (media?.categoriesHiddenStatus != null && isAdded) {
|
||||||
|
// Filter category name attribute from all categories
|
||||||
|
val categories = media.categoriesHiddenStatus.keys.map { key ->
|
||||||
|
var value = key
|
||||||
|
// Each category returned has a format like "Category:<some-category-name>"
|
||||||
|
// so remove the prefix "Category:"
|
||||||
|
if (key.startsWith("Category:")) {
|
||||||
|
value = key.substring(9)
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
val catString = categories.joinToString(", ")
|
||||||
|
if (catString.isNotEmpty() && binding?.tvReviewQuestionContext != null) {
|
||||||
|
val formattedCatString = "<b>$catString</b>"
|
||||||
|
val stringToConvertHtml = getString(
|
||||||
|
R.string.review_category_explanation,
|
||||||
|
formattedCatString
|
||||||
|
)
|
||||||
|
return Html.fromHtml(stringToConvertHtml).toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getString(R.string.review_no_category)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
position = requireArguments().getInt("position")
|
||||||
|
binding = FragmentReviewImageBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
val question: String
|
||||||
|
var explanation: String? = null
|
||||||
|
val yesButtonText: String
|
||||||
|
val noButtonText: String
|
||||||
|
|
||||||
|
binding?.buttonYes?.setOnClickListener { onYesButtonClicked() }
|
||||||
|
|
||||||
|
when (position) {
|
||||||
|
SPAM -> {
|
||||||
|
question = getString(R.string.review_spam)
|
||||||
|
explanation = getString(R.string.review_spam_explanation)
|
||||||
|
yesButtonText = getString(R.string.yes)
|
||||||
|
noButtonText = getString(R.string.no)
|
||||||
|
binding?.buttonNo?.setOnClickListener {
|
||||||
|
reviewActivity.reviewController.reportSpam(requireActivity(), reviewCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
COPYRIGHT -> {
|
||||||
|
enableButtons()
|
||||||
|
question = getString(R.string.review_copyright)
|
||||||
|
explanation = getString(R.string.review_copyright_explanation)
|
||||||
|
yesButtonText = getString(R.string.yes)
|
||||||
|
noButtonText = getString(R.string.no)
|
||||||
|
binding?.buttonNo?.setOnClickListener {
|
||||||
|
reviewActivity.reviewController.reportPossibleCopyRightViolation(
|
||||||
|
requireActivity(),
|
||||||
|
reviewCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CATEGORY -> {
|
||||||
|
enableButtons()
|
||||||
|
question = getString(R.string.review_category)
|
||||||
|
explanation = updateCategoriesQuestion()
|
||||||
|
yesButtonText = getString(R.string.yes)
|
||||||
|
noButtonText = getString(R.string.no)
|
||||||
|
binding?.buttonNo?.setOnClickListener {
|
||||||
|
reviewActivity.reviewController.reportWrongCategory(
|
||||||
|
requireActivity(),
|
||||||
|
reviewCallback
|
||||||
|
)
|
||||||
|
reviewActivity.swipeToNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
THANKS -> {
|
||||||
|
enableButtons()
|
||||||
|
question = getString(R.string.review_thanks)
|
||||||
|
|
||||||
|
user = reviewActivity.reviewController.firstRevision?.user
|
||||||
|
?: savedInstanceState?.getString(SAVED_USER)
|
||||||
|
|
||||||
|
//if the user is null because of whatsoever reason, review will not be sent anyways
|
||||||
|
if (!user.isNullOrEmpty()) {
|
||||||
|
explanation = getString(R.string.review_thanks_explanation, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that the yes and no buttons are swapped in this section
|
||||||
|
yesButtonText = getString(R.string.review_thanks_yes_button_text)
|
||||||
|
noButtonText = getString(R.string.review_thanks_no_button_text)
|
||||||
|
binding?.buttonYes?.setTextColor(Color.parseColor("#116aaa"))
|
||||||
|
binding?.buttonNo?.setTextColor(Color.parseColor("#228b22"))
|
||||||
|
binding?.buttonNo?.setOnClickListener {
|
||||||
|
reviewActivity.reviewController.sendThanks(requireActivity())
|
||||||
|
reviewActivity.swipeToNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
enableButtons()
|
||||||
|
question = "How did we get here?"
|
||||||
|
explanation = "No idea."
|
||||||
|
yesButtonText = "yes"
|
||||||
|
noButtonText = "no"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding?.apply {
|
||||||
|
tvReviewQuestion.text = question
|
||||||
|
tvReviewQuestionContext.text = explanation
|
||||||
|
buttonYes.text = yesButtonText
|
||||||
|
buttonNo.text = noButtonText
|
||||||
|
}
|
||||||
|
return binding?.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called when configuration changes happen
|
||||||
|
*
|
||||||
|
* @param outState
|
||||||
|
*/
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
//Save user name when configuration changes happen
|
||||||
|
outState.putString(SAVED_USER, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val reviewCallback: ReviewController.ReviewCallback
|
||||||
|
get() = object : ReviewController.ReviewCallback {
|
||||||
|
override fun onSuccess() {
|
||||||
|
reviewActivity.runRandomizer()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure() {
|
||||||
|
//do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTokenException(e: Exception) {
|
||||||
|
if (e is InvalidLoginTokenException) {
|
||||||
|
val username = sessionManager.userName
|
||||||
|
val logoutListener = activity?.let {
|
||||||
|
CommonsApplication.BaseLogoutListener(
|
||||||
|
it,
|
||||||
|
getString(R.string.invalid_login_message),
|
||||||
|
username
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logoutListener != null) {
|
||||||
|
CommonsApplication.instance.clearApplicationData(
|
||||||
|
requireActivity(), logoutListener
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disableButtons() {
|
||||||
|
this@ReviewImageFragment.disableButtons()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enableButtons() {
|
||||||
|
this@ReviewImageFragment.enableButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when an image has
|
||||||
|
* been loaded to enable the review buttons.
|
||||||
|
*/
|
||||||
|
fun enableButtons() {
|
||||||
|
binding?.apply {
|
||||||
|
buttonYes.isEnabled = true
|
||||||
|
buttonYes.alpha = 1f
|
||||||
|
buttonNo.isEnabled = true
|
||||||
|
buttonNo.alpha = 1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when an image is being loaded
|
||||||
|
* to disable the review buttons
|
||||||
|
*/
|
||||||
|
fun disableButtons() {
|
||||||
|
binding?.apply {
|
||||||
|
buttonYes.isEnabled = false
|
||||||
|
buttonYes.alpha = 0.5f
|
||||||
|
buttonNo.isEnabled = false
|
||||||
|
buttonNo.alpha = 0.5f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onYesButtonClicked() {
|
||||||
|
reviewActivity.swipeToNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val reviewActivity: ReviewActivity
|
||||||
|
get() = requireActivity() as ReviewActivity
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package fr.free.nrw.commons.review;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
|
||||||
|
|
||||||
public class ReviewPagerAdapter extends FragmentStatePagerAdapter {
|
|
||||||
private ReviewImageFragment[] reviewImageFragments;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this function return the instance of ReviewviewPage current item
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
|
||||||
return super.instantiateItem(container, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReviewPagerAdapter(FragmentManager fm) {
|
|
||||||
super(fm);
|
|
||||||
reviewImageFragments = new ReviewImageFragment[]{
|
|
||||||
new ReviewImageFragment(),
|
|
||||||
new ReviewImageFragment(),
|
|
||||||
new ReviewImageFragment(),
|
|
||||||
new ReviewImageFragment()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return reviewImageFragments.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateFileInformation() {
|
|
||||||
for (int i = 0; i < getCount(); i++) {
|
|
||||||
ReviewImageFragment fragment = reviewImageFragments[i];
|
|
||||||
fragment.update(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment getItem(int position) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putInt("position", position);
|
|
||||||
reviewImageFragments[position].setArguments(bundle);
|
|
||||||
return reviewImageFragments[position];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package fr.free.nrw.commons.review
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
|
||||||
|
private val reviewImageFragments: Array<ReviewImageFragment> = arrayOf(
|
||||||
|
ReviewImageFragment(),
|
||||||
|
ReviewImageFragment(),
|
||||||
|
ReviewImageFragment(),
|
||||||
|
ReviewImageFragment()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return reviewImageFragments.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateFileInformation() {
|
||||||
|
for (i in 0 until count) {
|
||||||
|
val fragment = reviewImageFragments[i]
|
||||||
|
fragment.update(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(position: Int): Fragment {
|
||||||
|
val bundle = Bundle().apply {
|
||||||
|
putInt("position", position)
|
||||||
|
}
|
||||||
|
reviewImageFragments[position].arguments = bundle
|
||||||
|
return reviewImageFragments[position]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package fr.free.nrw.commons.review
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.MotionEvent
|
||||||
|
|
||||||
|
import androidx.viewpager.widget.ViewPager
|
||||||
|
|
||||||
|
class ReviewViewPager @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null
|
||||||
|
) : ViewPager(context, attrs) {
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
// Never allow swiping to switch between pages
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
// Never allow swiping to switch between pages
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue