mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Converted AchievementsFragment to kotlin
This commit is contained in:
parent
440eaec5e7
commit
4fcbb81e5d
2 changed files with 493 additions and 492 deletions
|
|
@ -1,492 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.achievements;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.databinding.FragmentAchievementsBinding;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import fr.free.nrw.commons.profile.ProfileActivity;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* fragment for sharing feedback on uploaded activity
|
||||
*/
|
||||
public class AchievementsFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
private static final double BADGE_IMAGE_WIDTH_RATIO = 0.4;
|
||||
private static final double BADGE_IMAGE_HEIGHT_RATIO = 0.3;
|
||||
|
||||
/**
|
||||
* Help link URLs
|
||||
*/
|
||||
private static final String IMAGES_UPLOADED_URL = "https://commons.wikimedia.org/wiki/Commons:Project_scope";
|
||||
private static final String IMAGES_REVERT_URL = "https://commons.wikimedia.org/wiki/Commons:Deletion_policy#Reasons_for_deletion";
|
||||
private static final String IMAGES_USED_URL = "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Images";
|
||||
private static final String IMAGES_NEARBY_PLACES_URL = "https://www.wikidata.org/wiki/Property:P18";
|
||||
private static final String IMAGES_FEATURED_URL = "https://commons.wikimedia.org/wiki/Commons:Featured_pictures";
|
||||
private static final String QUALITY_IMAGE_URL = "https://commons.wikimedia.org/wiki/Commons:Quality_images";
|
||||
private static final String THANKS_URL = "https://www.mediawiki.org/wiki/Extension:Thanks";
|
||||
|
||||
private LevelController.LevelInfo levelInfo;
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
|
||||
@Inject
|
||||
OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
|
||||
private FragmentAchievementsBinding binding;
|
||||
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
// To keep track of the number of wiki edits made by a user
|
||||
private int numberOfEdits = 0;
|
||||
|
||||
private String userName;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getArguments() != null) {
|
||||
userName = getArguments().getString(ProfileActivity.KEY_USERNAME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method helps in the creation Achievement screen and
|
||||
* dynamically set the size of imageView
|
||||
*
|
||||
* @param savedInstanceState Data bundle
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentAchievementsBinding.inflate(inflater, container, false);
|
||||
View rootView = binding.getRoot();
|
||||
|
||||
binding.achievementInfo.setOnClickListener(view -> showInfoDialog());
|
||||
binding.imagesUploadInfo.setOnClickListener(view -> showUploadInfo());
|
||||
binding.imagesRevertedInfo.setOnClickListener(view -> showRevertedInfo());
|
||||
binding.imagesUsedByWikiInfo.setOnClickListener(view -> showUsedByWikiInfo());
|
||||
binding.imagesNearbyInfo.setOnClickListener(view -> showImagesViaNearbyInfo());
|
||||
binding.imagesFeaturedInfo.setOnClickListener(view -> showFeaturedImagesInfo());
|
||||
binding.thanksReceivedInfo.setOnClickListener(view -> showThanksReceivedInfo());
|
||||
binding.qualityImagesInfo.setOnClickListener(view -> showQualityImagesInfo());
|
||||
|
||||
// DisplayMetrics used to fetch the size of the screen
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||
int height = displayMetrics.heightPixels;
|
||||
int width = displayMetrics.widthPixels;
|
||||
|
||||
// Used for the setting the size of imageView at runtime
|
||||
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams)
|
||||
binding.achievementBadgeImage.getLayoutParams();
|
||||
params.height = (int) (height * BADGE_IMAGE_HEIGHT_RATIO);
|
||||
params.width = (int) (width * BADGE_IMAGE_WIDTH_RATIO);
|
||||
binding.achievementBadgeImage.requestLayout();
|
||||
binding.progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// Set the initial value of WikiData edits to 0
|
||||
binding.wikidataEdits.setText("0");
|
||||
if(sessionManager.getUserName() == null || sessionManager.getUserName().equals(userName)){
|
||||
binding.tvAchievementsOfUser.setVisibility(View.GONE);
|
||||
}else{
|
||||
binding.tvAchievementsOfUser.setVisibility(View.VISIBLE);
|
||||
binding.tvAchievementsOfUser.setText(getString(R.string.achievements_of_user,userName));
|
||||
}
|
||||
|
||||
// Achievements currently unimplemented in Beta flavor. Skip all API calls.
|
||||
if(ConfigUtils.isBetaFlavour()) {
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
binding.imagesUsedByWikiText.setText(R.string.no_image);
|
||||
binding.imagesRevertedText.setText(R.string.no_image_reverted);
|
||||
binding.imagesUploadTextParam.setText(R.string.no_image_uploaded);
|
||||
binding.wikidataEdits.setText("0");
|
||||
binding.imageFeatured.setText("0");
|
||||
binding.qualityImages.setText("0");
|
||||
binding.achievementLevel.setText("0");
|
||||
setMenuVisibility(true);
|
||||
return rootView;
|
||||
}
|
||||
setWikidataEditCount();
|
||||
setAchievements();
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
binding = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuVisibility(boolean visible) {
|
||||
super.setMenuVisibility(visible);
|
||||
|
||||
// Whenever this fragment is revealed in a menu,
|
||||
// notify Beta users the page data is unavailable
|
||||
if(ConfigUtils.isBetaFlavour() && visible) {
|
||||
Context ctx = null;
|
||||
if(getContext() != null) {
|
||||
ctx = getContext();
|
||||
} else if(getView() != null && getView().getContext() != null) {
|
||||
ctx = getView().getContext();
|
||||
}
|
||||
if(ctx != null) {
|
||||
Toast.makeText(ctx,
|
||||
R.string.achievements_unavailable_beta,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To invoke the AlertDialog on clicking info button
|
||||
*/
|
||||
protected void showInfoDialog(){
|
||||
launchAlert(
|
||||
getResources().getString(R.string.Achievements),
|
||||
getResources().getString(R.string.achievements_info_message));
|
||||
}
|
||||
|
||||
/**
|
||||
* To call the API to get results in form Single<JSONObject>
|
||||
* which then calls parseJson when results are fetched
|
||||
*/
|
||||
private void setAchievements() {
|
||||
binding.progressBar.setVisibility(View.VISIBLE);
|
||||
if (checkAccount()) {
|
||||
try{
|
||||
|
||||
compositeDisposable.add(okHttpJsonApiClient
|
||||
.getAchievements(Objects.requireNonNull(userName))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
response -> {
|
||||
if (response != null) {
|
||||
setUploadCount(Achievements.from(response));
|
||||
} else {
|
||||
Timber.d("success");
|
||||
binding.layoutImageReverts.setVisibility(View.INVISIBLE);
|
||||
binding.achievementBadgeImage.setVisibility(View.INVISIBLE);
|
||||
// If the number of edits made by the user are more than 150,000
|
||||
// in some cases such high number of wiki edit counts cause the
|
||||
// achievements calculator to fail in some cases, for more details
|
||||
// refer Issue: #3295
|
||||
if (numberOfEdits <= 150000) {
|
||||
showSnackBarWithRetry(false);
|
||||
} else {
|
||||
showSnackBarWithRetry(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
t -> {
|
||||
Timber.e(t, "Fetching achievements statistics failed");
|
||||
if (numberOfEdits <= 150000) {
|
||||
showSnackBarWithRetry(false);
|
||||
} else {
|
||||
showSnackBarWithRetry(true);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
catch (Exception e){
|
||||
Timber.d(e+"success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To call the API to fetch the count of wiki data edits
|
||||
* in the form of JavaRx Single object<JSONobject>
|
||||
*/
|
||||
private void setWikidataEditCount() {
|
||||
if (StringUtils.isBlank(userName)) {
|
||||
return;
|
||||
}
|
||||
compositeDisposable.add(okHttpJsonApiClient
|
||||
.getWikidataEdits(userName)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(edits -> {
|
||||
numberOfEdits = edits;
|
||||
binding.wikidataEdits.setText(String.valueOf(edits));
|
||||
}, e -> {
|
||||
Timber.e("Error:" + e);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a snack bar which has an action button which on click dismisses the snackbar and invokes the
|
||||
* listener passed
|
||||
* @param tooManyAchievements if this value is true it means that the number of achievements of the
|
||||
* user are so high that it wrecks havoc with the Achievements calculator due to which request may time
|
||||
* out. Well this is the Ultimate Achievement
|
||||
*/
|
||||
private void showSnackBarWithRetry(boolean tooManyAchievements) {
|
||||
if (tooManyAchievements) {
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
ViewUtil.showDismissibleSnackBar(getActivity().findViewById(android.R.id.content),
|
||||
R.string.achievements_fetch_failed_ultimate_achievement, R.string.retry, view -> setAchievements());
|
||||
} else {
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
ViewUtil.showDismissibleSnackBar(getActivity().findViewById(android.R.id.content),
|
||||
R.string.achievements_fetch_failed, R.string.retry, view -> setAchievements());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a generic error toast when error occurs while loading achievements or uploads
|
||||
*/
|
||||
private void onError() {
|
||||
ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.error_occurred));
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* used to the count of images uploaded by user
|
||||
*/
|
||||
private void setUploadCount(Achievements achievements) {
|
||||
if (checkAccount()) {
|
||||
compositeDisposable.add(okHttpJsonApiClient
|
||||
.getUploadCount(Objects.requireNonNull(userName))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
uploadCount -> setAchievementsUploadCount(achievements, uploadCount),
|
||||
t -> {
|
||||
Timber.e(t, "Fetching upload count failed");
|
||||
onError();
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* used to set achievements upload count and call hideProgressbar
|
||||
* @param uploadCount
|
||||
*/
|
||||
private void setAchievementsUploadCount(Achievements achievements, int uploadCount) {
|
||||
// Create a new instance of Achievements with updated imagesUploaded
|
||||
Achievements updatedAchievements = new Achievements(
|
||||
achievements.getUniqueUsedImages(),
|
||||
achievements.getArticlesUsingImages(),
|
||||
achievements.getThanksReceived(),
|
||||
achievements.getFeaturedImages(),
|
||||
achievements.getQualityImages(),
|
||||
uploadCount, // Update imagesUploaded with new value
|
||||
achievements.getRevertCount()
|
||||
);
|
||||
|
||||
hideProgressBar(updatedAchievements);
|
||||
}
|
||||
|
||||
/**
|
||||
* used to the uploaded images progressbar
|
||||
* @param uploadCount
|
||||
*/
|
||||
private void setUploadProgress(int uploadCount){
|
||||
if (uploadCount==0){
|
||||
setZeroAchievements();
|
||||
}else {
|
||||
binding.imagesUploadedProgressbar.setVisibility(View.VISIBLE);
|
||||
binding.imagesUploadedProgressbar.setProgress
|
||||
(100*uploadCount/levelInfo.getMaxUploadCount());
|
||||
binding.tvUploadedImages.setText
|
||||
(uploadCount + "/" + levelInfo.getMaxUploadCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setZeroAchievements() {
|
||||
String message = !Objects.equals(sessionManager.getUserName(), userName) ?
|
||||
getString(R.string.no_achievements_yet, userName) :
|
||||
getString(R.string.you_have_no_achievements_yet);
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
null,
|
||||
message,
|
||||
getString(R.string.ok),
|
||||
() -> {},
|
||||
true);
|
||||
// binding.imagesUploadedProgressbar.setVisibility(View.INVISIBLE);
|
||||
// binding.imageRevertsProgressbar.setVisibility(View.INVISIBLE);
|
||||
// binding.imagesUsedByWikiProgressBar.setVisibility(View.INVISIBLE);
|
||||
binding.achievementBadgeImage.setVisibility(View.INVISIBLE);
|
||||
binding.imagesUsedByWikiText.setText(R.string.no_image);
|
||||
binding.imagesRevertedText.setText(R.string.no_image_reverted);
|
||||
binding.imagesUploadTextParam.setText(R.string.no_image_uploaded);
|
||||
binding.achievementBadgeImage.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* used to set the non revert image percentage
|
||||
* @param notRevertPercentage
|
||||
*/
|
||||
private void setImageRevertPercentage(int notRevertPercentage){
|
||||
binding.imageRevertsProgressbar.setVisibility(View.VISIBLE);
|
||||
binding.imageRevertsProgressbar.setProgress(notRevertPercentage);
|
||||
final String revertPercentage = Integer.toString(notRevertPercentage);
|
||||
binding.tvRevertedImages.setText(revertPercentage + "%");
|
||||
binding.imagesRevertLimitText.setText(getResources().getString(R.string.achievements_revert_limit_message)+ levelInfo.getMinNonRevertPercentage() + "%");
|
||||
}
|
||||
|
||||
/**
|
||||
* Used the inflate the fetched statistics of the images uploaded by user
|
||||
* and assign badge and level. Also stores the achievements level of the user in BasicKvStore to display in menu
|
||||
* @param achievements
|
||||
*/
|
||||
private void inflateAchievements(Achievements achievements) {
|
||||
// binding.imagesUsedByWikiProgressBar.setVisibility(View.VISIBLE);
|
||||
binding.thanksReceived.setText(String.valueOf(achievements.getThanksReceived()));
|
||||
binding.imagesUsedByWikiProgressBar.setProgress
|
||||
(100 * achievements.getUniqueUsedImages() / levelInfo.getMaxUniqueImages());
|
||||
binding.tvWikiPb.setText(achievements.getUniqueUsedImages() + "/"
|
||||
+ levelInfo.getMaxUniqueImages());
|
||||
binding.imageFeatured.setText(String.valueOf(achievements.getFeaturedImages()));
|
||||
binding.qualityImages.setText(String.valueOf(achievements.getQualityImages()));
|
||||
String levelUpInfoString = getString(R.string.level).toUpperCase(Locale.ROOT);
|
||||
levelUpInfoString += " " + levelInfo.getLevelNumber();
|
||||
binding.achievementLevel.setText(levelUpInfoString);
|
||||
binding.achievementBadgeImage.setImageDrawable(VectorDrawableCompat.create(getResources(), R.drawable.badge,
|
||||
new ContextThemeWrapper(getActivity(), levelInfo.getLevelStyle()).getTheme()));
|
||||
binding.achievementBadgeText.setText(Integer.toString(levelInfo.getLevelNumber()));
|
||||
BasicKvStore store = new BasicKvStore(this.getContext(), userName);
|
||||
store.putString("userAchievementsLevel", Integer.toString(levelInfo.getLevelNumber()));
|
||||
}
|
||||
|
||||
/**
|
||||
* to hide progressbar
|
||||
*/
|
||||
private void hideProgressBar(Achievements achievements) {
|
||||
if (binding.progressBar != null) {
|
||||
levelInfo = LevelController.LevelInfo.from(achievements.getImagesUploaded(),
|
||||
achievements.getUniqueUsedImages(),
|
||||
achievements.getNotRevertPercentage());
|
||||
inflateAchievements(achievements);
|
||||
setUploadProgress(achievements.getImagesUploaded());
|
||||
setImageRevertPercentage(achievements.getNotRevertPercentage());
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
protected void showUploadInfo(){
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.images_uploaded),
|
||||
getResources().getString(R.string.images_uploaded_explanation),
|
||||
IMAGES_UPLOADED_URL);
|
||||
}
|
||||
|
||||
protected void showRevertedInfo(){
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.image_reverts),
|
||||
getResources().getString(R.string.images_reverted_explanation),
|
||||
IMAGES_REVERT_URL);
|
||||
}
|
||||
|
||||
protected void showUsedByWikiInfo(){
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.images_used_by_wiki),
|
||||
getResources().getString(R.string.images_used_explanation),
|
||||
IMAGES_USED_URL);
|
||||
}
|
||||
|
||||
protected void showImagesViaNearbyInfo(){
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_wikidata_edits),
|
||||
getResources().getString(R.string.images_via_nearby_explanation),
|
||||
IMAGES_NEARBY_PLACES_URL);
|
||||
}
|
||||
|
||||
protected void showFeaturedImagesInfo(){
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_featured),
|
||||
getResources().getString(R.string.images_featured_explanation),
|
||||
IMAGES_FEATURED_URL);
|
||||
}
|
||||
|
||||
protected void showThanksReceivedInfo(){
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_thanks),
|
||||
getResources().getString(R.string.thanks_received_explanation),
|
||||
THANKS_URL);
|
||||
}
|
||||
|
||||
public void showQualityImagesInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_quality),
|
||||
getResources().getString(R.string.quality_images_info),
|
||||
QUALITY_IMAGE_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* takes title and message as input to display alerts
|
||||
* @param title
|
||||
* @param message
|
||||
*/
|
||||
private void launchAlert(String title, String message){
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
title,
|
||||
message,
|
||||
getString(R.string.ok),
|
||||
() -> {},
|
||||
true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch Alert with a READ MORE button and clicking it open a custom webpage
|
||||
*/
|
||||
private void launchAlertWithHelpLink(String title, String message, String helpLinkUrl) {
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
title,
|
||||
message,
|
||||
getString(R.string.ok),
|
||||
getString(R.string.read_help_link),
|
||||
() -> {},
|
||||
() -> Utils.handleWebUrl(requireContext(), Uri.parse(helpLinkUrl)),
|
||||
null,
|
||||
true);
|
||||
}
|
||||
|
||||
/**
|
||||
* check to ensure that user is logged in
|
||||
* @return
|
||||
*/
|
||||
private boolean checkAccount(){
|
||||
Account currentAccount = sessionManager.getCurrentAccount();
|
||||
if (currentAccount == null) {
|
||||
Timber.d("Current account is null");
|
||||
ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.user_not_logged_in));
|
||||
sessionManager.forceLogin(getActivity());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,493 @@
|
|||
package fr.free.nrw.commons.profile.achievements
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.Utils
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.databinding.FragmentAchievementsBinding
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.profile.ProfileActivity
|
||||
import fr.free.nrw.commons.profile.achievements.Achievements.Companion.from
|
||||
import fr.free.nrw.commons.profile.achievements.LevelController.LevelInfo
|
||||
import fr.free.nrw.commons.profile.achievements.LevelController.LevelInfo.Companion.from
|
||||
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
|
||||
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showDismissibleSnackBar
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* fragment for sharing feedback on uploaded activity
|
||||
*/
|
||||
class AchievementsFragment : CommonsDaggerSupportFragment() {
|
||||
@Inject
|
||||
lateinit var sessionManager: SessionManager
|
||||
|
||||
@Inject
|
||||
lateinit var okHttpJsonApiClient: OkHttpJsonApiClient
|
||||
|
||||
private var levelInfo: LevelInfo? = null
|
||||
private var binding: FragmentAchievementsBinding? = null
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
// To keep track of the number of wiki edits made by a user
|
||||
private var numberOfEdits = 0
|
||||
private var userName: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (arguments != null) {
|
||||
userName = arguments!!.getString(ProfileActivity.KEY_USERNAME)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method helps in the creation Achievement screen and
|
||||
* dynamically set the size of imageView
|
||||
*
|
||||
* @param savedInstanceState Data bundle
|
||||
*/
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentAchievementsBinding.inflate(inflater, container, false)
|
||||
val rootView: View = binding!!.root
|
||||
|
||||
binding!!.achievementInfo.setOnClickListener { showInfoDialog() }
|
||||
binding!!.imagesUploadInfo.setOnClickListener { showUploadInfo() }
|
||||
binding!!.imagesRevertedInfo.setOnClickListener { showRevertedInfo() }
|
||||
binding!!.imagesUsedByWikiInfo.setOnClickListener { showUsedByWikiInfo() }
|
||||
binding!!.imagesNearbyInfo.setOnClickListener { showImagesViaNearbyInfo() }
|
||||
binding!!.imagesFeaturedInfo.setOnClickListener { showFeaturedImagesInfo() }
|
||||
binding!!.thanksReceivedInfo.setOnClickListener { showThanksReceivedInfo() }
|
||||
binding!!.qualityImagesInfo.setOnClickListener { showQualityImagesInfo() }
|
||||
|
||||
// DisplayMetrics used to fetch the size of the screen
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
val height = displayMetrics.heightPixels
|
||||
val width = displayMetrics.widthPixels
|
||||
|
||||
// Used for the setting the size of imageView at runtime
|
||||
val params = binding!!.achievementBadgeImage.layoutParams as ConstraintLayout.LayoutParams
|
||||
params.height = (height * BADGE_IMAGE_HEIGHT_RATIO).toInt()
|
||||
params.width = (width * BADGE_IMAGE_WIDTH_RATIO).toInt()
|
||||
binding!!.achievementBadgeImage.requestLayout()
|
||||
binding!!.progressBar.visibility = View.VISIBLE
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
// Set the initial value of WikiData edits to 0
|
||||
binding!!.wikidataEdits.text = "0"
|
||||
if (sessionManager.userName == null || sessionManager.userName == userName) {
|
||||
binding!!.tvAchievementsOfUser.visibility = View.GONE
|
||||
} else {
|
||||
binding!!.tvAchievementsOfUser.visibility = View.VISIBLE
|
||||
binding!!.tvAchievementsOfUser.text =
|
||||
getString(R.string.achievements_of_user, userName)
|
||||
}
|
||||
|
||||
// Achievements currently unimplemented in Beta flavor. Skip all API calls.
|
||||
if (isBetaFlavour) {
|
||||
binding!!.progressBar.visibility = View.GONE
|
||||
binding!!.imagesUsedByWikiText.setText(R.string.no_image)
|
||||
binding!!.imagesRevertedText.setText(R.string.no_image_reverted)
|
||||
binding!!.imagesUploadTextParam.setText(R.string.no_image_uploaded)
|
||||
binding!!.wikidataEdits.text = "0"
|
||||
binding!!.imageFeatured.text = "0"
|
||||
binding!!.qualityImages.text = "0"
|
||||
binding!!.achievementLevel.text = "0"
|
||||
setMenuVisibility(true)
|
||||
return rootView
|
||||
}
|
||||
setWikidataEditCount()
|
||||
setAchievements()
|
||||
return rootView
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun setMenuVisibility(visible: Boolean) {
|
||||
super.setMenuVisibility(visible)
|
||||
|
||||
// Whenever this fragment is revealed in a menu,
|
||||
// notify Beta users the page data is unavailable
|
||||
if (isBetaFlavour && visible) {
|
||||
val ctx: Context? = if (context != null) {
|
||||
context
|
||||
} else if (view != null && requireView().context != null) {
|
||||
requireView().context
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
ctx?.let {
|
||||
Toast.makeText(it, R.string.achievements_unavailable_beta, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To invoke the AlertDialog on clicking info button
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun showInfoDialog() = launchAlert(
|
||||
resources.getString(R.string.Achievements),
|
||||
resources.getString(R.string.achievements_info_message)
|
||||
)
|
||||
|
||||
/**
|
||||
* To call the API to get results in form Single<JSONObject>
|
||||
* which then calls parseJson when results are fetched
|
||||
</JSONObject> */
|
||||
private fun setAchievements() {
|
||||
binding!!.progressBar.visibility = View.VISIBLE
|
||||
if (checkAccount()) {
|
||||
try {
|
||||
compositeDisposable.add(
|
||||
okHttpJsonApiClient.getAchievements(userName)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ response: FeedbackResponse? ->
|
||||
if (response != null) {
|
||||
setUploadCount(from(response))
|
||||
} else {
|
||||
Timber.d("success")
|
||||
binding!!.layoutImageReverts.visibility = View.INVISIBLE
|
||||
binding!!.achievementBadgeImage.visibility = View.INVISIBLE
|
||||
|
||||
// If the number of edits made by the user are more than 150,000
|
||||
// in some cases such high number of wiki edit counts cause the
|
||||
// achievements calculator to fail in some cases, for more details
|
||||
// refer Issue: #3295
|
||||
if (numberOfEdits <= 150000) {
|
||||
showSnackBarWithRetry(false)
|
||||
} else {
|
||||
showSnackBarWithRetry(true)
|
||||
}
|
||||
}
|
||||
}, { t: Throwable? ->
|
||||
Timber.e(t, "Fetching achievements statistics failed")
|
||||
if (numberOfEdits <= 150000) {
|
||||
showSnackBarWithRetry(false)
|
||||
} else {
|
||||
showSnackBarWithRetry(true)
|
||||
}
|
||||
}))
|
||||
} catch (e: Exception) {
|
||||
Timber.d(e, "success")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To call the API to fetch the count of wiki data edits
|
||||
* in the form of JavaRx Single object<JSONobject>
|
||||
</JSONobject> */
|
||||
private fun setWikidataEditCount() {
|
||||
if (StringUtils.isBlank(userName)) {
|
||||
return
|
||||
}
|
||||
compositeDisposable.add(
|
||||
okHttpJsonApiClient.getWikidataEdits(userName)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ edits: Int ->
|
||||
numberOfEdits = edits
|
||||
binding!!.wikidataEdits.text = edits.toString()
|
||||
}, { e: Throwable ->
|
||||
Timber.e(e,"Error")
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a snack bar which has an action button which on click dismisses the snackbar and invokes the
|
||||
* listener passed
|
||||
* @param tooManyAchievements if this value is true it means that the number of achievements of the
|
||||
* user are so high that it wrecks havoc with the Achievements calculator due to which request may time
|
||||
* out. Well this is the Ultimate Achievement
|
||||
*/
|
||||
private fun showSnackBarWithRetry(tooManyAchievements: Boolean) {
|
||||
binding!!.progressBar.visibility = View.GONE
|
||||
showDismissibleSnackBar(
|
||||
view = requireActivity().findViewById(android.R.id.content),
|
||||
messageResourceId = if (tooManyAchievements) {
|
||||
R.string.achievements_fetch_failed_ultimate_achievement
|
||||
} else {
|
||||
R.string.achievements_fetch_failed
|
||||
},
|
||||
actionButtonResourceId = R.string.retry
|
||||
) { setAchievements() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a generic error toast when error occurs while loading achievements or uploads
|
||||
*/
|
||||
private fun onError() {
|
||||
showLongToast(requireActivity(), resources.getString(R.string.error_occurred))
|
||||
binding!!.progressBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
/**
|
||||
* used to the count of images uploaded by user
|
||||
*/
|
||||
private fun setUploadCount(achievements: Achievements) {
|
||||
if (checkAccount()) {
|
||||
compositeDisposable.add(
|
||||
okHttpJsonApiClient.getUploadCount(userName)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ uploadCount: Int ->
|
||||
setAchievementsUploadCount(achievements, uploadCount)
|
||||
},
|
||||
{ t: Throwable? ->
|
||||
Timber.e(t, "Fetching upload count failed")
|
||||
onError()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* used to set achievements upload count and call hideProgressbar
|
||||
* @param uploadCount
|
||||
*/
|
||||
private fun setAchievementsUploadCount(achievements: Achievements, uploadCount: Int) =
|
||||
hideProgressBar(achievements.copy(imagesUploaded = uploadCount))
|
||||
|
||||
/**
|
||||
* used to the uploaded images progressbar
|
||||
* @param uploadCount
|
||||
*/
|
||||
private fun setUploadProgress(uploadCount: Int) {
|
||||
if (uploadCount == 0) {
|
||||
setZeroAchievements()
|
||||
} else {
|
||||
binding!!.imagesUploadedProgressbar.visibility = View.VISIBLE
|
||||
binding!!.imagesUploadedProgressbar.progress =
|
||||
100 * uploadCount / levelInfo!!.maxUploadCount
|
||||
binding!!.tvUploadedImages.text =
|
||||
uploadCount.toString() + "/" + levelInfo!!.maxUploadCount
|
||||
}
|
||||
}
|
||||
|
||||
private fun setZeroAchievements() {
|
||||
val message = if (sessionManager.userName != userName) getString(
|
||||
R.string.no_achievements_yet,
|
||||
userName
|
||||
) else getString(
|
||||
R.string.you_have_no_achievements_yet
|
||||
)
|
||||
showAlertDialog(requireActivity(), null, message, getString(R.string.ok), {}, true)
|
||||
binding!!.achievementBadgeImage.visibility = View.INVISIBLE
|
||||
binding!!.imagesUsedByWikiText.setText(R.string.no_image)
|
||||
binding!!.imagesRevertedText.setText(R.string.no_image_reverted)
|
||||
binding!!.imagesUploadTextParam.setText(R.string.no_image_uploaded)
|
||||
binding!!.achievementBadgeImage.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* used to set the non revert image percentage
|
||||
* @param notRevertPercentage
|
||||
*/
|
||||
private fun setImageRevertPercentage(notRevertPercentage: Int) {
|
||||
binding!!.imageRevertsProgressbar.visibility = View.VISIBLE
|
||||
binding!!.imageRevertsProgressbar.progress = notRevertPercentage
|
||||
val revertPercentage = notRevertPercentage.toString()
|
||||
binding!!.tvRevertedImages.text = "$revertPercentage%"
|
||||
binding!!.imagesRevertLimitText.text =
|
||||
resources.getString(R.string.achievements_revert_limit_message) + levelInfo!!.minNonRevertPercentage + "%"
|
||||
}
|
||||
|
||||
/**
|
||||
* Used the inflate the fetched statistics of the images uploaded by user
|
||||
* and assign badge and level. Also stores the achievements level of the user in BasicKvStore to display in menu
|
||||
* @param achievements
|
||||
*/
|
||||
private fun inflateAchievements(achievements: Achievements) = with(binding!!) {
|
||||
thanksReceived.text = achievements.thanksReceived.toString()
|
||||
imagesUsedByWikiProgressBar.progress =
|
||||
100 * achievements.uniqueUsedImages / levelInfo!!.maxUniqueImages
|
||||
tvWikiPb.text = (achievements.uniqueUsedImages.toString() + "/"
|
||||
+ levelInfo!!.maxUniqueImages)
|
||||
imageFeatured.text = achievements.featuredImages.toString()
|
||||
qualityImages.text = achievements.qualityImages.toString()
|
||||
var levelUpInfoString = getString(R.string.level).uppercase()
|
||||
levelUpInfoString += " " + levelInfo!!.levelNumber
|
||||
achievementLevel.text = levelUpInfoString
|
||||
achievementBadgeImage.setImageDrawable(
|
||||
VectorDrawableCompat.create(
|
||||
resources, R.drawable.badge,
|
||||
ContextThemeWrapper(activity, levelInfo!!.levelStyle).theme
|
||||
)
|
||||
)
|
||||
achievementBadgeText.text = levelInfo!!.levelNumber.toString()
|
||||
val store = BasicKvStore(requireContext(), userName)
|
||||
store.putString("userAchievementsLevel", levelInfo!!.levelNumber.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* to hide progressbar
|
||||
*/
|
||||
private fun hideProgressBar(achievements: Achievements) {
|
||||
if (binding?.progressBar != null) {
|
||||
levelInfo = from(
|
||||
achievements.imagesUploaded,
|
||||
achievements.uniqueUsedImages,
|
||||
achievements.notRevertPercentage
|
||||
)
|
||||
inflateAchievements(achievements)
|
||||
setUploadProgress(achievements.imagesUploaded)
|
||||
setImageRevertPercentage(achievements.notRevertPercentage)
|
||||
binding!!.progressBar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showUploadInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
resources.getString(R.string.images_uploaded),
|
||||
resources.getString(R.string.images_uploaded_explanation),
|
||||
IMAGES_UPLOADED_URL
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showRevertedInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
resources.getString(R.string.image_reverts),
|
||||
resources.getString(R.string.images_reverted_explanation),
|
||||
IMAGES_REVERT_URL
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showUsedByWikiInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
resources.getString(R.string.images_used_by_wiki),
|
||||
resources.getString(R.string.images_used_explanation),
|
||||
IMAGES_USED_URL
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showImagesViaNearbyInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
resources.getString(R.string.statistics_wikidata_edits),
|
||||
resources.getString(R.string.images_via_nearby_explanation),
|
||||
IMAGES_NEARBY_PLACES_URL
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showFeaturedImagesInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
resources.getString(R.string.statistics_featured),
|
||||
resources.getString(R.string.images_featured_explanation),
|
||||
IMAGES_FEATURED_URL
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showThanksReceivedInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
resources.getString(R.string.statistics_thanks),
|
||||
resources.getString(R.string.thanks_received_explanation),
|
||||
THANKS_URL
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun showQualityImagesInfo() {
|
||||
launchAlertWithHelpLink(
|
||||
resources.getString(R.string.statistics_quality),
|
||||
resources.getString(R.string.quality_images_info),
|
||||
QUALITY_IMAGE_URL
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* takes title and message as input to display alerts
|
||||
* @param title
|
||||
* @param message
|
||||
*/
|
||||
private fun launchAlert(title: String, message: String) =
|
||||
showAlertDialog(requireActivity(), title, message, getString(R.string.ok), {}, true)
|
||||
|
||||
/**
|
||||
* Launch Alert with a READ MORE button and clicking it open a custom webpage
|
||||
*/
|
||||
private fun launchAlertWithHelpLink(title: String, message: String, helpLinkUrl: String) =
|
||||
showAlertDialog(
|
||||
requireActivity(), title, message,
|
||||
getString(R.string.ok),
|
||||
getString(R.string.read_help_link),
|
||||
{},
|
||||
{ Utils.handleWebUrl(requireContext(), Uri.parse(helpLinkUrl)) },
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
/**
|
||||
* check to ensure that user is logged in
|
||||
* @return
|
||||
*/
|
||||
private fun checkAccount(): Boolean {
|
||||
val currentAccount = sessionManager.currentAccount
|
||||
if (currentAccount == null) {
|
||||
Timber.d("Current account is null")
|
||||
showLongToast(requireActivity(), resources.getString(R.string.user_not_logged_in))
|
||||
sessionManager.forceLogin(activity)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BADGE_IMAGE_WIDTH_RATIO = 0.4
|
||||
private const val BADGE_IMAGE_HEIGHT_RATIO = 0.3
|
||||
|
||||
/**
|
||||
* Help link URLs
|
||||
*/
|
||||
private const val IMAGES_UPLOADED_URL =
|
||||
"https://commons.wikimedia.org/wiki/Commons:Project_scope"
|
||||
private const val IMAGES_REVERT_URL =
|
||||
"https://commons.wikimedia.org/wiki/Commons:Deletion_policy#Reasons_for_deletion"
|
||||
private const val IMAGES_USED_URL =
|
||||
"https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Images"
|
||||
private const val IMAGES_NEARBY_PLACES_URL =
|
||||
"https://www.wikidata.org/wiki/Property:P18"
|
||||
private const val IMAGES_FEATURED_URL =
|
||||
"https://commons.wikimedia.org/wiki/Commons:Featured_pictures"
|
||||
private const val QUALITY_IMAGE_URL =
|
||||
"https://commons.wikimedia.org/wiki/Commons:Quality_images"
|
||||
private const val THANKS_URL =
|
||||
"https://www.mediawiki.org/wiki/Extension:Thanks"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue