diff --git a/app/build.gradle b/app/build.gradle
index de49c6b93..ea06c5c43 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -21,6 +21,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.5.1'
implementation 'info.debatty:java-string-similarity:0.24'
implementation 'com.borjabravo:readmoretextview:2.1.0'
+
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.5.0@aar') {
transitive = true
@@ -59,6 +60,7 @@ dependencies {
testImplementation 'org.robolectric:robolectric:3.7.1'
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
+ implementation 'com.dinuscxj:circleprogressbar:1.1.1'
implementation 'com.caverock:androidsvg:1.2.1'
implementation 'com.github.bumptech.glide:glide:4.7.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5bd0dc523..3665042ac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,3 +1,4 @@
+
@@ -108,8 +109,11 @@
android:parentActivityName=".contributions.ContributionsActivity"
/>
-
+
+
-
+
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java
index 9447d173a..4e4b46b01 100644
--- a/app/src/main/java/fr/free/nrw/commons/Utils.java
+++ b/app/src/main/java/fr/free/nrw/commons/Utils.java
@@ -2,11 +2,13 @@ package fr.free.nrw.commons;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.customtabs.CustomTabsIntent;
import android.support.v4.content.ContextCompat;
+import android.view.View;
import android.widget.Toast;
import org.apache.commons.codec.binary.Hex;
@@ -150,7 +152,7 @@ public class Utils {
StringBuilder stringBuilder = new StringBuilder();
try {
- String[] command = new String[] {"logcat","-d","-v","threadtime"};
+ String[] command = new String[]{"logcat","-d","-v","threadtime"};
Process process = Runtime.getRuntime().exec(command);
@@ -199,4 +201,18 @@ public class Utils {
customTabsIntent.launchUrl(context, url);
}
+ /**
+ * To take screenshot of the screen and return it in Bitmap format
+ *
+ * @param view
+ * @return
+ */
+ public static Bitmap getScreenShot(View view) {
+ View screenView = view.getRootView();
+ screenView.setDrawingCacheEnabled(true);
+ Bitmap bitmap = Bitmap.createBitmap(screenView.getDrawingCache());
+ screenView.setDrawingCacheEnabled(false);
+ return bitmap;
+ }
+
}
diff --git a/app/src/main/java/fr/free/nrw/commons/achievements/Achievements.java b/app/src/main/java/fr/free/nrw/commons/achievements/Achievements.java
new file mode 100644
index 000000000..5fac8da9a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/achievements/Achievements.java
@@ -0,0 +1,205 @@
+package fr.free.nrw.commons.achievements;
+
+import android.util.Log;
+
+/**
+ * represnts Achievements class ans stores all the parameters
+ */
+public class Achievements {
+ private int uniqueUsedImages;
+ private int articlesUsingImages;
+ private int thanksReceived;
+ private int imagesEditedBySomeoneElse;
+ private int featuredImages;
+ private int imagesUploaded;
+ private int revertCount;
+
+ public Achievements(){
+
+ }
+
+ /**
+ * constructor for achievements class to set its data members
+ * @param uniqueUsedImages
+ * @param articlesUsingImages
+ * @param thanksReceived
+ * @param imagesEditedBySomeoneElse
+ * @param featuredImages
+ * @param imagesUploaded
+ * @param revertCount
+ */
+ public Achievements(int uniqueUsedImages,
+ int articlesUsingImages,
+ int thanksReceived,
+ int imagesEditedBySomeoneElse,
+ int featuredImages,
+ int imagesUploaded,
+ int revertCount) {
+ this.uniqueUsedImages = uniqueUsedImages;
+ this.articlesUsingImages = articlesUsingImages;
+ this.thanksReceived = thanksReceived;
+ this.imagesEditedBySomeoneElse = imagesEditedBySomeoneElse;
+ this.featuredImages = featuredImages;
+ this.imagesUploaded = imagesUploaded;
+ this.revertCount = revertCount;
+ }
+
+ /**
+ * Builder class for Achievements class
+ */
+ public class AchievementsBuilder {
+ private int nestedUniqueUsedImages;
+ private int nestedArticlesUsingImages;
+ private int nestedThanksReceived;
+ private int nestedImagesEditedBySomeoneElse;
+ private int nestedFeaturedImages;
+ private int nestedImagesUploaded;
+ private int nestedRevertCount;
+
+ public AchievementsBuilder setUniqueUsedImages(int uniqueUsedImages) {
+ this.nestedUniqueUsedImages = uniqueUsedImages;
+ return this;
+ }
+
+ public AchievementsBuilder setArticlesUsingImages(int articlesUsingImages) {
+ this.nestedArticlesUsingImages = articlesUsingImages;
+ return this;
+ }
+
+ public AchievementsBuilder setThanksReceived(int thanksReceived) {
+ this.nestedThanksReceived = thanksReceived;
+ return this;
+ }
+
+ public AchievementsBuilder setImagesEditedBySomeoneElse(int imagesEditedBySomeoneElse) {
+ this.nestedImagesEditedBySomeoneElse = imagesEditedBySomeoneElse;
+ return this;
+ }
+
+ public AchievementsBuilder setFeaturedImages(int featuredImages) {
+ this.nestedFeaturedImages = featuredImages;
+ return this;
+ }
+
+ public AchievementsBuilder setImagesUploaded(int imagesUploaded) {
+ this.nestedImagesUploaded = imagesUploaded;
+ return this;
+ }
+
+ public AchievementsBuilder setRevertCount( int revertCount){
+ this.nestedRevertCount = revertCount;
+ return this;
+ }
+
+ public Achievements createAchievements(){
+ return new Achievements(nestedUniqueUsedImages,
+ nestedArticlesUsingImages,
+ nestedThanksReceived,
+ nestedImagesEditedBySomeoneElse,
+ nestedFeaturedImages,
+ nestedImagesUploaded,
+ nestedRevertCount);
+ }
+
+ }
+
+ /**
+ * getter function to get count of images uploaded
+ * @return
+ */
+ public int getImagesUploaded() {
+ return imagesUploaded;
+ }
+
+ /**
+ * getter function to get count of featured images
+ * @return
+ */
+ public int getFeaturedImages() {
+ return featuredImages;
+ }
+
+ /**
+ * getter function to get count of thanks received
+ * @return
+ */
+ public int getThanksReceived() {
+ return thanksReceived;
+ }
+
+ /**
+ * getter function to get count of unique images used by wiki
+ * @return
+ */
+ public int getUniqueUsedImages() {
+ return uniqueUsedImages;
+ }
+
+ /**
+ * setter function to count of images uploaded
+ * @param imagesUploaded
+ */
+ public void setImagesUploaded(int imagesUploaded) {
+ this.imagesUploaded = imagesUploaded;
+ }
+
+ /**
+ * setter function to set count of featured images
+ * @param featuredImages
+ */
+ public void setFeaturedImages(int featuredImages) {
+ this.featuredImages = featuredImages;
+ }
+
+ /**
+ * setter function to set the count of images edited by someone
+ * @param imagesEditedBySomeoneElse
+ */
+ public void setImagesEditedBySomeoneElse(int imagesEditedBySomeoneElse) {
+ this.imagesEditedBySomeoneElse = imagesEditedBySomeoneElse;
+ }
+
+ /**
+ * setter function to set count of thanks received
+ * @param thanksReceived
+ */
+ public void setThanksReceived(int thanksReceived) {
+ this.thanksReceived = thanksReceived;
+ }
+
+ /**
+ * setter function to count of articles using images uploaded
+ * @param articlesUsingImages
+ */
+ public void setArticlesUsingImages(int articlesUsingImages) {
+ this.articlesUsingImages = articlesUsingImages;
+ }
+
+ /**
+ * setter function to set count of uniques images used by wiki
+ * @param uniqueUsedImages
+ */
+ public void setUniqueUsedImages(int uniqueUsedImages) {
+ this.uniqueUsedImages = uniqueUsedImages;
+ }
+
+ /**
+ * to set count of images reverted
+ * @param revertCount
+ */
+ public void setRevertCount(int revertCount) {
+ this.revertCount = revertCount;
+ }
+
+ /**
+ * used to calculate the percentages of images that haven't been reverted
+ * @return
+ */
+ public int getNotRevertPercentage(){
+ try {
+ return ((imagesUploaded - revertCount) * 100)/imagesUploaded;
+ } catch (ArithmeticException divideByZero ){
+ return 100;
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java b/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java
new file mode 100644
index 000000000..905d0dfbd
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java
@@ -0,0 +1,458 @@
+package fr.free.nrw.commons.achievements;
+
+import android.accounts.Account;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.Toolbar;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.dinuscxj.progressbar.CircleProgressBar;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import fr.free.nrw.commons.theme.NavigationBaseActivity;
+import fr.free.nrw.commons.utils.ViewUtil;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
+import timber.log.Timber;
+
+/**
+ * activity for sharing feedback on uploaded activity
+ */
+public class AchievementsActivity extends NavigationBaseActivity {
+
+ private static final double BADGE_IMAGE_WIDTH_RATIO = 0.4;
+ private static final double BADGE_IMAGE_HEIGHT_RATIO = 0.3;
+ private Boolean isUploadFetched = false;
+ private Boolean isStatisticsFetched = false;
+ private Boolean isRevertFetched = false;
+ private Achievements achievements = new Achievements();
+ private LevelController.LevelInfo levelInfo;
+
+ @BindView(R.id.achievement_badge)
+ ImageView imageView;
+ @BindView(R.id.achievement_level)
+ TextView levelNumber;
+ @BindView(R.id.toolbar)
+ Toolbar toolbar;
+ @BindView(R.id.thanks_received)
+ TextView thanksReceived;
+ @BindView(R.id.images_uploaded_progressbar)
+ CircleProgressBar imagesUploadedProgressbar;
+ @BindView(R.id.images_used_by_wiki_progressbar)
+ CircleProgressBar imagesUsedByWikiProgessbar;
+ @BindView(R.id.image_reverts_progressbar)
+ CircleProgressBar imageRevertsProgressbar;
+ @BindView(R.id.image_featured)
+ TextView imagesFeatured;
+ @BindView(R.id.images_revert_limit_text)
+ TextView imagesRevertLimitText;
+ @BindView(R.id.progressBar)
+ ProgressBar progressBar;
+ @BindView(R.id.layout_image_uploaded)
+ RelativeLayout layoutImageUploaded;
+ @BindView(R.id.layout_image_reverts)
+ RelativeLayout layoutImageReverts;
+ @BindView(R.id.layout_image_used_by_wiki)
+ RelativeLayout layoutImageUsedByWiki;
+ @BindView(R.id.layout_statistics)
+ LinearLayout layoutStatistics;
+ @Inject
+ SessionManager sessionManager;
+ @Inject
+ MediaWikiApi mediaWikiApi;
+
+ private CompositeDisposable compositeDisposable = new CompositeDisposable();
+
+ /**
+ * This method helps in the creation Achievement screen and
+ * dynamically set the size of imageView
+ *
+ * @param savedInstanceState Data bundle
+ */
+ @Override
+ @SuppressLint("StringFormatInvalid")
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_achievements);
+ ButterKnife.bind(this);
+ /**
+ * DisplayMetrics used to fetch the size of the screen
+ */
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ int height = displayMetrics.heightPixels;
+ int width = displayMetrics.widthPixels;
+
+ /**
+ * Used for the setting the size of imageView at runtime
+ */
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)
+ imageView.getLayoutParams();
+ params.height = (int) (height * BADGE_IMAGE_HEIGHT_RATIO);
+ params.width = (int) (width * BADGE_IMAGE_WIDTH_RATIO);
+ imageView.setImageResource(R.drawable.badge);
+ imageView.requestLayout();
+
+ setSupportActionBar(toolbar);
+ progressBar.setVisibility(View.VISIBLE);
+ hideLayouts();
+ setAchievements();
+ setUploadCount();
+ setRevertCount();
+ initDrawer();
+ }
+
+ /**
+ * to invoke the AlertDialog on clicking info button
+ */
+ @OnClick(R.id.achievement_info)
+ public void showInfoDialog(){
+ launchAlert(getResources().getString(R.string.Achievements)
+ ,getResources().getString(R.string.achievements_info_message));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_about, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.share_app_icon) {
+ View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
+ Bitmap screenShot = Utils.getScreenShot(rootView);
+ showAlert(screenShot);
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * To take bitmap and store it temporary storage and share it
+ *
+ * @param bitmap
+ */
+ void shareScreen(Bitmap bitmap) {
+ try {
+ File file = new File(this.getExternalCacheDir(), "screen.png");
+ FileOutputStream fOut = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
+ fOut.flush();
+ fOut.close();
+ file.setReadable(true, false);
+ final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
+ intent.setType("image/png");
+ startActivity(Intent.createChooser(intent, "Share image via"));
+ } catch (IOException e) {
+ //Do Nothing
+ }
+ }
+
+ /**
+ * To call the API to get results in form Single
+ * which then calls parseJson when results are fetched
+ */
+ private void setAchievements() {
+ if(checkAccount()) {
+ compositeDisposable.add(mediaWikiApi
+ .getAchievements(sessionManager.getCurrentAccount().name)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ jsonObject -> parseJson(jsonObject),
+ t -> Timber.e(t, "Fetching achievements statisticss failed")
+ ));
+ }
+ }
+
+ /**
+ * To call the API to get reverts count in form of JSONObject
+ *
+ */
+
+ private void setRevertCount(){
+ if(checkAccount()) {
+ compositeDisposable.add(mediaWikiApi
+ .getRevertCount(sessionManager.getCurrentAccount().name)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ object -> parseJsonRevertCount(object),
+ t -> Timber.e(t, "Fetching revert count failed")
+ ));
+ }
+ }
+
+ /**
+ * used to set number of deleted images
+ * @param object
+ */
+ private void parseJsonRevertCount(JSONObject object){
+ try {
+ achievements.setRevertCount(object.getInt("deletedUploads"));
+ } catch (JSONException e) {
+ Timber.d( e, e.getMessage());
+ }
+ isRevertFetched = true;
+ hideProgressBar();
+ }
+
+ /**
+ * used to the count of images uploaded by user
+ */
+ private void setUploadCount() {
+ if(checkAccount()) {
+ compositeDisposable.add(mediaWikiApi
+ .getUploadCount(sessionManager.getCurrentAccount().name)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ uploadCount -> setAchievementsUploadCount(uploadCount),
+ t -> Timber.e(t, "Fetching upload count failed")
+ ));
+ }
+ }
+
+ /**
+ * used to set achievements upload count and call hideProgressbar
+ * @param uploadCount
+ */
+ private void setAchievementsUploadCount(int uploadCount){
+ achievements.setImagesUploaded(uploadCount);
+ isUploadFetched = true;
+ hideProgressBar();
+ }
+
+ /**
+ * used to the uploaded images progressbar
+ * @param uploadCount
+ */
+ private void setUploadProgress(int uploadCount){
+ imagesUploadedProgressbar.setProgress
+ (100*uploadCount/levelInfo.getMaxUploadCount());
+ imagesUploadedProgressbar.setProgressTextFormatPattern
+ (uploadCount +"/" + levelInfo.getMaxUploadCount() );
+ }
+
+ /**
+ * used to set the non revert image percentage
+ * @param notRevertPercentage
+ */
+ private void setImageRevertPercentage(int notRevertPercentage){
+ imageRevertsProgressbar.setProgress(notRevertPercentage);
+ String revertPercentage = Integer.toString(notRevertPercentage);
+ imageRevertsProgressbar.setProgressTextFormatPattern(revertPercentage + "%%");
+ imagesRevertLimitText.setText(getResources().getString(R.string.achievements_revert_limit_message)+ levelInfo.getMinNonRevertPercentage() + "%");
+ }
+
+ /**
+ * used to parse the JSONObject containing results
+ * @param object
+ */
+ private void parseJson(JSONObject object) {
+ try {
+ achievements.setUniqueUsedImages(object.getInt("uniqueUsedImages"));
+ achievements.setArticlesUsingImages(object.getInt("articlesUsingImages"));
+ achievements.setThanksReceived(object.getInt("thanksReceived"));
+ achievements.setImagesEditedBySomeoneElse(object.getInt("imagesEditedBySomeoneElse"));
+ JSONObject featuredImages = object.getJSONObject("featuredImages");
+ achievements.setFeaturedImages
+ (featuredImages.getInt("Quality_images") +
+ featuredImages.getInt("Featured_pictures_on_Wikimedia_Commons"));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ isStatisticsFetched = true;
+ hideProgressBar();
+ }
+
+ /**
+ * Used the inflate the fetched statistics of the images uploaded by user
+ * and assign badge and level
+ * @param achievements
+ */
+ private void inflateAchievements(Achievements achievements ){
+ thanksReceived.setText(Integer.toString(achievements.getThanksReceived()));
+ imagesUsedByWikiProgessbar.setProgress
+ (100*achievements.getUniqueUsedImages()/levelInfo.getMaxUniqueImages() );
+ imagesUsedByWikiProgessbar.setProgressTextFormatPattern
+ (achievements.getUniqueUsedImages() + "/" + levelInfo.getMaxUniqueImages());
+ imagesFeatured.setText(Integer.toString(achievements.getFeaturedImages()));
+ String levelUpInfoString = getString(R.string.level);
+ levelUpInfoString += " " + Integer.toString(levelInfo.getLevelNumber());
+ levelNumber.setText(levelUpInfoString);
+ final ContextThemeWrapper wrapper = new ContextThemeWrapper(this, levelInfo.getLevelStyle());
+ Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.badge, wrapper.getTheme());
+ Bitmap bitmap = BitmapUtils.drawableToBitmap(drawable);
+ BitmapDrawable bitmapImage = BitmapUtils.writeOnDrawable(bitmap, Integer.toString(levelInfo.getLevelNumber()),this);
+ imageView.setImageDrawable(bitmapImage);
+ }
+
+ /**
+ * Creates a way to change current activity to AchievementActivity
+ * @param context
+ */
+ public static void startYourself(Context context) {
+ Intent intent = new Intent(context, AchievementsActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ context.startActivity(intent);
+ }
+
+ /**
+ * to hide progressbar
+ */
+ private void hideProgressBar() {
+ if (progressBar != null && isUploadFetched && isStatisticsFetched && isRevertFetched) {
+ levelInfo = LevelController.LevelInfo.from(achievements.getImagesUploaded(),
+ achievements.getUniqueUsedImages(),
+ achievements.getNotRevertPercentage());
+ inflateAchievements(achievements);
+ setUploadProgress(achievements.getImagesUploaded());
+ setImageRevertPercentage(achievements.getNotRevertPercentage());
+ progressBar.setVisibility(View.GONE);
+ layoutImageReverts.setVisibility(View.VISIBLE);
+ layoutImageUploaded.setVisibility(View.VISIBLE);
+ layoutImageUsedByWiki.setVisibility(View.VISIBLE);
+ layoutStatistics.setVisibility(View.VISIBLE);
+ imageView.setVisibility(View.VISIBLE);
+ levelNumber.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * used to hide the layouts while fetching results from api
+ */
+ private void hideLayouts(){
+ layoutImageUsedByWiki.setVisibility(View.INVISIBLE);
+ layoutImageUploaded.setVisibility(View.INVISIBLE);
+ layoutImageReverts.setVisibility(View.INVISIBLE);
+ layoutStatistics.setVisibility(View.INVISIBLE);
+ imageView.setVisibility(View.INVISIBLE);
+ levelNumber.setVisibility(View.INVISIBLE);
+ }
+
+ /**
+ * It display the alertDialog with Image of screenshot
+ * @param screenshot
+ */
+ public void showAlert(Bitmap screenshot){
+ AlertDialog.Builder alertadd = new AlertDialog.Builder(AchievementsActivity.this);
+ LayoutInflater factory = LayoutInflater.from(AchievementsActivity.this);
+ final View view = factory.inflate(R.layout.image_alert_layout, null);
+ ImageView screenShotImage = (ImageView) view.findViewById(R.id.alert_image);
+ screenShotImage.setImageBitmap(screenshot);
+ TextView shareMessage = (TextView) view.findViewById(R.id.alert_text);
+ shareMessage.setText(R.string.achievements_share_message);
+ alertadd.setView(view);
+ alertadd.setPositiveButton("Proceed", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ shareScreen(screenshot);
+ }
+ });
+ alertadd.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ alertadd.show();
+ }
+
+ @OnClick(R.id.images_upload_info)
+ public void showUploadInfo(){
+ launchAlert(getResources().getString(R.string.images_uploaded)
+ ,getResources().getString(R.string.images_uploaded_explanation));
+ }
+
+ @OnClick(R.id.images_reverted_info)
+ public void showRevertedInfo(){
+ launchAlert(getResources().getString(R.string.image_reverts)
+ ,getResources().getString(R.string.images_reverted_explanation));
+ }
+
+ @OnClick(R.id.images_used_by_wiki_info)
+ public void showUsedByWikiInfo(){
+ launchAlert(getResources().getString(R.string.images_used_by_wiki)
+ ,getResources().getString(R.string.images_used_explanation));
+ }
+
+ /**
+ * takes title and message as input to display alerts
+ * @param title
+ * @param message
+ */
+ private void launchAlert(String title, String message){
+ new AlertDialog.Builder(AchievementsActivity.this)
+ .setTitle(title)
+ .setMessage(message)
+ .setCancelable(true)
+ .setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
+ .create()
+ .show();
+ }
+
+ /**
+ * 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(this, getResources().getString(R.string.user_not_logged_in));
+ sessionManager.forceLogin(this);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/achievements/BitmapUtils.java b/app/src/main/java/fr/free/nrw/commons/achievements/BitmapUtils.java
new file mode 100644
index 000000000..b7400117d
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/achievements/BitmapUtils.java
@@ -0,0 +1,54 @@
+package fr.free.nrw.commons.achievements;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+public class BitmapUtils {
+
+ /**
+ * write level Number on the badge
+ * @param bm
+ * @param text
+ * @return
+ */
+ public static BitmapDrawable writeOnDrawable(Bitmap bm, String text, Context context){
+ Bitmap.Config config = bm.getConfig();
+ if(config == null){
+ config = Bitmap.Config.ARGB_8888;
+ }
+ Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),config);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawBitmap(bm, 0, 0, null);
+ Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(Color.WHITE);
+ paint.setTextSize(Math.round(canvas.getHeight()/2));
+ paint.setTextAlign(Paint.Align.CENTER);
+ Rect rectText = new Rect();
+ paint.getTextBounds(text,0, text.length(),rectText);
+ canvas.drawText(text, Math.round(canvas.getWidth()/2),Math.round(canvas.getHeight()/1.35), paint);
+ return new BitmapDrawable(context.getResources(), bitmap);
+ }
+
+ /**
+ * Convert Drawable to bitmap
+ * @param drawable
+ * @return
+ */
+ public static Bitmap drawableToBitmap (Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable)drawable).getBitmap();
+ }
+ Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/achievements/LevelController.java b/app/src/main/java/fr/free/nrw/commons/achievements/LevelController.java
new file mode 100644
index 000000000..e0f84bbee
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/achievements/LevelController.java
@@ -0,0 +1,85 @@
+package fr.free.nrw.commons.achievements;
+
+import android.util.Log;
+
+import fr.free.nrw.commons.R;
+
+/**
+ * calculates the level of the user
+ */
+public class LevelController {
+
+ public LevelInfo level;
+ public enum LevelInfo{
+ LEVEL_1(1, R.style.LevelOne, 5, 20, 85),
+ LEVEL_2(2, R.style.LevelTwo, 10, 30, 86),
+ LEVEL_3(3, R.style.LevelThree, 15,40, 87),
+ LEVEL_4(4, R.style.LevelFour,20,50, 88),
+ LEVEL_5(5, R.style.LevelFive, 25, 60, 89),
+ LEVEL_6(6,R.style.LevelOne,30,70, 90),
+ LEVEL_7(7, R.style.LevelTwo, 40, 80, 90),
+ LEVEL_8(8, R.style.LevelThree, 45, 90, 90),
+ LEVEL_9(9, R.style.LevelFour, 50, 100, 90),
+ LEVEL_10(10, R.style.LevelFive, 55, 110, 90),
+ LEVEL_11(11,R.style.LevelOne, 60, 120, 90),
+ LEVEL_12(12,R.style.LevelTwo,65 , 130, 90),
+ LEVEL_13(13,R.style.LevelThree, 70, 140, 90),
+ LEVEL_14(14,R.style.LevelFour, 75 , 150, 90),
+ LEVEL_15(15,R.style.LevelFive, 80, 160, 90);
+
+ private int levelNumber;
+ private int levelStyle;
+ private int maxUniqueImages;
+ private int maxUploadCount;
+ private int minNonRevertPercentage;
+
+ LevelInfo(int levelNumber,
+ int levelStyle,
+ int maxUniqueImages,
+ int maxUploadCount,
+ int minNonRevertPercentage) {
+ this.levelNumber = levelNumber;
+ this.levelStyle = levelStyle;
+ this.maxUniqueImages = maxUniqueImages;
+ this.maxUploadCount = maxUploadCount;
+ this.minNonRevertPercentage = minNonRevertPercentage;
+ }
+
+ public static LevelInfo from(int imagesUploaded,
+ int uniqueImagesUsed,
+ int nonRevertRate) {
+ LevelInfo level = LEVEL_15;
+
+ for (LevelInfo levelInfo : LevelInfo.values()) {
+ if (imagesUploaded < levelInfo.maxUploadCount
+ || uniqueImagesUsed < levelInfo.maxUniqueImages
+ || nonRevertRate < levelInfo.minNonRevertPercentage ) {
+ level = levelInfo;
+ return level;
+ }
+ }
+ return level;
+ }
+
+ public int getLevelStyle() {
+ return levelStyle;
+ }
+
+ public int getLevelNumber() {
+ return levelNumber;
+ }
+
+ public int getMaxUniqueImages() {
+ return maxUniqueImages;
+ }
+
+ public int getMaxUploadCount() {
+ return maxUploadCount;
+ }
+
+ public int getMinNonRevertPercentage(){
+ return minNonRevertPercentage;
+ }
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
index b6c0a2046..7d7443b95 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
@@ -135,11 +135,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
}
}
- public static void startYourself(Context context) {
- Intent intent = new Intent(context, LoginActivity.class);
- context.startActivity(intent);
- }
-
private void forgotPassword() {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
}
@@ -445,4 +440,9 @@ public class LoginActivity extends AccountAuthenticatorActivity {
loginButton.setEnabled(enabled);
}
}
+
+ public static void startYourself(Context context) {
+ Intent intent = new Intent(context, LoginActivity.class);
+ context.startActivity(intent);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
index 48248225e..70ffec55f 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
@@ -4,12 +4,14 @@ import dagger.Module;
import dagger.android.ContributesAndroidInjector;
import fr.free.nrw.commons.AboutActivity;
import fr.free.nrw.commons.WelcomeActivity;
+import fr.free.nrw.commons.achievements.AchievementsActivity;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.auth.SignupActivity;
import fr.free.nrw.commons.category.CategoryDetailsActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.category.CategoryImagesActivity;
import fr.free.nrw.commons.explore.SearchActivity;
+
import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
@@ -58,4 +60,8 @@ public abstract class ActivityBuilderModule {
@ContributesAndroidInjector
abstract CategoryDetailsActivity bindCategoryDetailsActivity();
+
+ @ContributesAndroidInjector
+ abstract AchievementsActivity bindAchievementsActivity();
+
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
index 495321cd2..6088b774b 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
@@ -23,6 +23,7 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils;
+import org.json.JSONObject;
import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import org.w3c.dom.Element;
@@ -53,6 +54,10 @@ import fr.free.nrw.commons.notification.NotificationUtils;
import in.yuvi.http.fluent.Http;
import io.reactivex.Observable;
import io.reactivex.Single;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
import timber.log.Timber;
import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue;
@@ -902,13 +907,11 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.param("meta", "userinfo")
.param("uiprop", "blockinfo")
.get();
- if(result != null) {
+ if (result != null) {
String blockEnd = result.getString("/api/query/userinfo/@blockexpiry");
- if(blockEnd.equals("infinite"))
- {
+ if (blockEnd.equals("infinite")) {
userBlocked = true;
- }
- else if (!blockEnd.isEmpty()) {
+ } else if (!blockEnd.isEmpty()) {
Date endDate = parseMWDate(blockEnd);
Date current = new Date();
userBlocked = endDate.after(current);
@@ -922,6 +925,68 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return userBlocked;
}
+ /**
+ * This takes userName as input, which is then used to fetch the feedback/achievements
+ * statistics using OkHttp and JavaRx. This function return JSONObject
+ * @param userName
+ * @return
+ */
+ @NonNull
+ @Override
+ public Single getAchievements(String userName) {
+ final String fetchAchievementUrlTemplate =
+ wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/feedback.py";
+ return Single.fromCallable(() -> {
+ String url = String.format(
+ Locale.ENGLISH,
+ fetchAchievementUrlTemplate,
+ new PageTitle(userName).getText());
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+ urlBuilder.addQueryParameter("user", userName);
+ Log.i("url", urlBuilder.toString());
+ Request request = new Request.Builder()
+ .url(urlBuilder.toString())
+ .build();
+ OkHttpClient client = new OkHttpClient();
+ Response response = client.newCall(request).execute();
+ String jsonData = response.body().string();
+ JSONObject jsonObject = new JSONObject(jsonData);
+ return jsonObject;
+ });
+
+ }
+
+ /**
+ * This takes userName as input, which is then used to fetch the no of images deleted
+ * using OkHttp and JavaRx. This function return JSONObject
+ * @param userName
+ * @return
+ */
+ @NonNull
+ @Override
+ public Single getRevertCount(String userName){
+ final String fetchRevertCountUrlTemplate =
+ wikiMediaToolforgeUrl + "urbanecmbot/commonsmisc/feedback.py";
+ return Single.fromCallable(() -> {
+ String url = String.format(
+ Locale.ENGLISH,
+ fetchRevertCountUrlTemplate,
+ new PageTitle(userName).getText());
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+ urlBuilder.addQueryParameter("user", userName);
+ urlBuilder.addQueryParameter("fetch","deletedUploads");
+ Log.i("url", urlBuilder.toString());
+ Request request = new Request.Builder()
+ .url(urlBuilder.toString())
+ .build();
+ OkHttpClient client = new OkHttpClient();
+ Response response = client.newCall(request).execute();
+ String jsonData = response.body().string();
+ JSONObject jsonRevertObject = new JSONObject(jsonData);
+ return jsonRevertObject;
+ });
+ }
+
private Date parseMWDate(String mwDate) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
index c8f6f4961..78c990372 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
@@ -3,6 +3,8 @@ package fr.free.nrw.commons.mwapi;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import org.json.JSONObject;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@@ -97,6 +99,12 @@ public interface MediaWikiApi {
boolean isUserBlockedFromCommons();
+ @NonNull
+ Single getAchievements(String userName);
+
+ @NonNull
+ Single getRevertCount(String userName);
+
interface ProgressListener {
void onProgress(long transferred, long total);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
index 7d975d2bf..8107a961a 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
@@ -15,6 +15,7 @@ import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -24,6 +25,7 @@ import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.WelcomeActivity;
+import fr.free.nrw.commons.achievements.AchievementsActivity;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
@@ -68,12 +70,19 @@ public abstract class NavigationBaseActivity extends BaseActivity
View navHeaderView = navigationView.getHeaderView(0);
TextView username = navHeaderView.findViewById(R.id.username);
-
AccountManager accountManager = AccountManager.get(this);
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
if (allAccounts.length != 0) {
username.setText(allAccounts[0].name);
}
+ ImageView userIcon = navHeaderView.findViewById(R.id.user_icon);
+ userIcon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ drawerLayout.closeDrawer(navigationView);
+ AchievementsActivity.startYourself(NavigationBaseActivity.this);
+ }
+ });
}
public void initBackButton() {
diff --git a/app/src/main/res/drawable-mdpi/badge.xml b/app/src/main/res/drawable-mdpi/badge.xml
new file mode 100644
index 000000000..3792e33cf
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/badge.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/featured.xml b/app/src/main/res/drawable/featured.xml
new file mode 100644
index 000000000..e971c3446
--- /dev/null
+++ b/app/src/main/res/drawable/featured.xml
@@ -0,0 +1,1069 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_info_outline_blue_24dp.xml b/app/src/main/res/drawable/ic_info_outline_blue_24dp.xml
new file mode 100644
index 000000000..e3f68ad02
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info_outline_blue_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_person_black_24dp.xml b/app/src/main/res/drawable/ic_person_black_24dp.xml
new file mode 100644
index 000000000..d7366bda0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_person_black_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_thanks.xml b/app/src/main/res/drawable/ic_thanks.xml
new file mode 100644
index 000000000..480b45c1b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_thanks.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_achievements.xml b/app/src/main/res/layout/activity_achievements.xml
new file mode 100644
index 000000000..293234f09
--- /dev/null
+++ b/app/src/main/res/layout/activity_achievements.xml
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/drawer_header.xml b/app/src/main/res/layout/drawer_header.xml
index 9bd3ae3a3..53a2684a0 100644
--- a/app/src/main/res/layout/drawer_header.xml
+++ b/app/src/main/res/layout/drawer_header.xml
@@ -1,5 +1,6 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/image_alert_layout.xml b/app/src/main/res/layout/image_alert_layout.xml
new file mode 100644
index 000000000..37b2c5b67
--- /dev/null
+++ b/app/src/main/res/layout/image_alert_layout.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 8d125aa61..a4c9ef716 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -23,4 +23,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 1db51fd8b..bad958661 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -50,6 +50,7 @@
#E0E0E0
#424242
+ #D6DCE0
#757575
#FFFFFF
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 1697853e8..bac816cd4 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -4,6 +4,7 @@
16dp
16dp
+ 8dp
48dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 51e2717af..8873d06df 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -76,8 +76,8 @@
- Starting %1$d uploads
- - %1$d upload
- - %1$d uploads
+ - %1$d upload
+ - %1$d uploads
No categories matching %1$s found
Add categories to make your images more discoverable on Wikimedia Commons.\nStart typing to add categories.
@@ -191,7 +191,7 @@
Unable to display more than 500
Set Recent Upload Limit
Two factor authentication is currently not supported.
- Do you really want to logout?
+ Do you really want to logout?
Commons Logo
Commons Website
Commons Facebook Page
@@ -306,5 +306,20 @@
Wallpaper set successfully!
Are you sure you want to clear your search history?
Search history deleted
+
+ Achievements
+ STATISTICS
+ Thanks Received
+ Featured Images
+ LEVEL
+ Images Uploaded
+ Images Not Reverted
+ Images Used
+ Share your achievements with your friends!
+ Your level increases as you meet these requirements. Items in the "statistics" section do not count towards your level.
+ minimum required:
+ The number of images you have uploaded to Commons, via any upload software
+ The percentage of images you have uploaded to Commons that were not deleted
+ The number of images you have uploaded to Commons that were used in Wikimedia articles
Login session expired, please log in again.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a8eb1d849..26d9c357b 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -57,4 +57,34 @@
- @android:color/transparent
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file