From bafae821e26bd71e351a6a97f4815aca6c502cd1 Mon Sep 17 00:00:00 2001 From: Saifuddin Adenwala Date: Sat, 23 Nov 2024 18:15:46 +0530 Subject: [PATCH] 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 --- .../navtab/MoreBottomSheetFragment.java | 2 +- .../nrw/commons/review/ReviewActivity.java | 334 ----------------- .../free/nrw/commons/review/ReviewActivity.kt | 336 ++++++++++++++++++ .../nrw/commons/review/ReviewController.java | 220 ------------ .../nrw/commons/review/ReviewController.kt | 231 ++++++++++++ .../review/{ReviewDao.java => ReviewDao.kt} | 21 +- .../free/nrw/commons/review/ReviewEntity.java | 19 - .../free/nrw/commons/review/ReviewEntity.kt | 13 + .../free/nrw/commons/review/ReviewHelper.kt | 4 +- .../commons/review/ReviewImageFragment.java | 262 -------------- .../nrw/commons/review/ReviewImageFragment.kt | 251 +++++++++++++ .../commons/review/ReviewPagerAdapter.java | 53 --- .../nrw/commons/review/ReviewPagerAdapter.kt | 37 ++ .../nrw/commons/review/ReviewViewPager.java | 30 -- .../nrw/commons/review/ReviewViewPager.kt | 25 ++ 15 files changed, 906 insertions(+), 932 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java create mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewController.java create mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt rename app/src/main/java/fr/free/nrw/commons/review/{ReviewDao.java => ReviewDao.kt} (54%) delete mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.java create mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.java create mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.java create mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.java create mode 100644 app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.kt diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.java b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.java index 0bd8333e3..9ea59488e 100644 --- a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.java @@ -232,7 +232,7 @@ public class MoreBottomSheetFragment extends BottomSheetDialogFragment { } protected void onPeerReviewClicked() { - ReviewActivity.startYourself(getActivity(), getString(R.string.title_activity_review)); + ReviewActivity.Companion.startYourself(getActivity(), getString(R.string.title_activity_review)); } } diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java deleted file mode 100644 index 40d743a19..000000000 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java +++ /dev/null @@ -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(); - } - } - -} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt new file mode 100644 index 000000000..44b0f9bc1 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt @@ -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(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() + } + } +} + diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewController.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewController.java deleted file mode 100644 index e3d5b2256..000000000 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewController.java +++ /dev/null @@ -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 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>) () -> - 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>) () -> 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(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt new file mode 100644 index 000000000..62652bd5b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewController.kt @@ -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 = 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() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewDao.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewDao.kt similarity index 54% rename from app/src/main/java/fr/free/nrw/commons/review/ReviewDao.java rename to app/src/main/java/fr/free/nrw/commons/review/ReviewDao.kt index c3e8c90a8..1dc9b6ae8 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewDao.java +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewDao.kt @@ -1,15 +1,15 @@ -package fr.free.nrw.commons.review; +package fr.free.nrw.commons.review -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query /** * Dao interface for reviewed images database */ @Dao -public interface ReviewDao { +interface ReviewDao { /** * Inserts reviewed/skipped image identifier into the database @@ -17,7 +17,7 @@ public interface ReviewDao { * @param reviewEntity */ @Insert(onConflict = OnConflictStrategy.IGNORE) - void insert(ReviewEntity reviewEntity); + fun insert(reviewEntity: ReviewEntity) /** * Checks if the image has already been reviewed/skipped by the user @@ -26,7 +26,6 @@ public interface ReviewDao { * @param imageId * @return */ - @Query( "SELECT EXISTS (SELECT * from `reviewed-images` where imageId = (:imageId))") - Boolean isReviewedAlready(String imageId); - -} \ No newline at end of file + @Query("SELECT EXISTS (SELECT * from `reviewed-images` where imageId = (:imageId))") + fun isReviewedAlready(imageId: String): Boolean +} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.java deleted file mode 100644 index 071111b15..000000000 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.kt new file mode 100644 index 000000000..473c143c7 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewEntity.kt @@ -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 +) diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt index 8a77c11ed..17296a5c8 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewHelper.kt @@ -77,7 +77,7 @@ class ReviewHelper * @param image * @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 @@ -132,7 +132,7 @@ class ReviewHelper */ fun addViewedImagesToDB(imageId: String?) { Completable - .fromAction { dao!!.insert(ReviewEntity(imageId)) } + .fromAction { imageId?.let { ReviewEntity(it) }?.let { dao!!.insert(it) } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.java deleted file mode 100644 index 7e0cd0ee3..000000000 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.java +++ /dev/null @@ -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 categories = new ArrayList<>(); - for(final String key : media.getCategoriesHiddenStatus().keySet()) { - String value = String.valueOf(key); - // Each category returned has a format like "Category:" - // 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 = "" + catString + ""; - 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; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt new file mode 100644 index 000000000..691c61f56 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewImageFragment.kt @@ -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:" + // 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 = "$catString" + 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 + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.java deleted file mode 100644 index 16b55c6e9..000000000 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.java +++ /dev/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]; - } - -} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.kt new file mode 100644 index 000000000..9bbe14e65 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewPagerAdapter.kt @@ -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 = 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] + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.java deleted file mode 100644 index 95740aac0..000000000 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.kt new file mode 100644 index 000000000..39de49189 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewViewPager.kt @@ -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 + } +}