diff --git a/app/build.gradle b/app/build.gradle index 5d37f8f54..25853684b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,8 +21,8 @@ dependencies { implementation 'info.debatty:java-string-similarity:0.24' implementation 'com.borjabravo:readmoretextview:2.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.4.1@aar'){ - transitive=true + implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.4.1@aar') { + transitive = true } implementation "com.github.deano2390:MaterialShowcaseView:1.2.0" @@ -68,6 +68,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' + compile 'com.dinuscxj:circleprogressbar:1.1.1' androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1' @@ -116,7 +117,8 @@ android { buildTypes { release { - minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient. + minifyEnabled false + // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } debug { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 17f6770d2..430e5f83c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,3 +1,4 @@ + @@ -14,7 +15,7 @@ - + @@ -23,17 +24,18 @@ android:name=".CommonsApplication" android:icon="@drawable/ic_launcher" android:label="@string/app_name" - android:theme="@style/LightAppTheme" - android:supportsRtl="true" > - - + android:supportsRtl="true" + android:theme="@style/LightAppTheme"> + + @@ -46,7 +48,9 @@ android:label="@string/app_name"> + + @@ -58,7 +62,9 @@ android:label="@string/app_name"> + + @@ -96,8 +102,12 @@ android:label="@string/title_activity_featured_images" android:parentActivityName=".contributions.ContributionsActivity" /> - + + + + @@ -128,6 +139,7 @@ + @@ -166,4 +178,4 @@ - + \ 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 91c23ce26..a27903ad6 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; @@ -106,7 +108,7 @@ public class Utils { /** * Fixing incorrect extension - * @param title File name + * @param title File name * @param extension Correct extension * @return File with correct extension */ @@ -146,7 +148,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); @@ -171,8 +173,7 @@ public class Utils { final String appPackageName = BuildConfig.class.getPackage().getName(); try { context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); - } - catch (android.content.ActivityNotFoundException anfe) { + } catch (android.content.ActivityNotFoundException anfe) { context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName))); } } @@ -194,4 +195,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..ea934c19b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/achievements/Achievements.java @@ -0,0 +1,5 @@ +package fr.free.nrw.commons.achievements; + +public class Achievements { + +} 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..57187be51 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/achievements/AchievementsActivity.java @@ -0,0 +1,171 @@ +package fr.free.nrw.commons.achievements; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.widget.Toolbar; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.json.JSONObject; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +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 io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + +/** + * 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; + + @BindView(R.id.achievement_badge) + ImageView imageView; + @BindView(R.id.achievement_level) + TextView textView; + @BindView(R.id.toolbar) + Toolbar toolbar; + @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.featured); + imageView.requestLayout(); + + setSupportActionBar(toolbar); + setAchievements(); + initDrawer(); + } + + @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); + shareScreen(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() { + compositeDisposable.add(mediaWikiApi + .getAchievements(sessionManager.getCurrentAccount().name) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + jsonObject -> parseJson(jsonObject) + )); + } + + /** + * used to parse the JSONObject containing results + * + * @param object + */ + private void parseJson(JSONObject object) { + Log.i("json", object.toString()); + } + + /** + * 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); + } + +} 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 51aa85903..20fadf624 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,10 +4,11 @@ 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.contributions.ContributionsActivity; import fr.free.nrw.commons.category.CategoryImagesActivity; +import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.nearby.NearbyActivity; import fr.free.nrw.commons.notification.NotificationActivity; import fr.free.nrw.commons.settings.SettingsActivity; @@ -50,4 +51,7 @@ public abstract class ActivityBuilderModule { @ContributesAndroidInjector abstract CategoryImagesActivity bindFeaturedImagesActivity(); + + @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 6629d0933..5056a0e12 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.NodeList; @@ -49,6 +50,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; @@ -217,7 +222,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { @Override public boolean pageExists(String pageName) throws IOException { - return Double.parseDouble( api.action("query") + return Double.parseDouble(api.action("query") .param("titles", pageName) .get() .getString("/api/query/pages/page/@_idx")) != -1; @@ -615,6 +620,37 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { }); } + /** + * 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; + }); + + } + 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 try { 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 c0bd2fd87..5cb5c4591 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; @@ -75,6 +77,9 @@ public interface MediaWikiApi { @NonNull Single getUploadCount(String userName); + @NonNull + Single getAchievements(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 4a7322b57..8a970ec52 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,20 @@ 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() { @@ -181,9 +191,10 @@ public abstract class NavigationBaseActivity extends BaseActivity public static void startActivityWithFlags(Context context, Class cls, int... flags) { Intent intent = new Intent(context, cls); - for (int flag: flags) { + for (int flag : flags) { intent.addFlags(flag); } context.startActivity(intent); } + } 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_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..99e1d28c1 --- /dev/null +++ b/app/src/main/res/layout/activity_achievements.xml @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/values/colors.xml b/app/src/main/res/values/colors.xml index ef5000d60..02dae825d 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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e30baa10..60080d208 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - Appearance + AppearanceGeneral Feedback Location @@ -74,8 +74,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. @@ -188,7 +188,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 @@ -284,4 +284,12 @@ Coordinates were not specified during image selection Error fetching nearby places. + Achievements + STATISTICS + Thanks Received + Featured Images + LEVEL + Images Uploaded + Images Not Reverted + Images Used By Wiki