From d3597b80a684e7bd306be6ee53ef0e5a29fcb4cc Mon Sep 17 00:00:00 2001 From: Ujjwal Agrawal Date: Thu, 3 May 2018 02:47:56 +0530 Subject: [PATCH 01/12] Added hideKeyboard in ViewUtil (#1488) --- .../free/nrw/commons/auth/LoginActivity.java | 15 +++---------- .../category/CategorizationFragment.java | 15 ++----------- .../upload/MultipleUploadListFragment.java | 14 +++--------- .../nrw/commons/upload/ShareActivity.java | 10 ++------- .../commons/upload/SingleUploadFragment.java | 22 ++++--------------- .../fr/free/nrw/commons/utils/ViewUtil.java | 11 ++++++++++ 6 files changed, 25 insertions(+), 62 deletions(-) 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 7b2b0a97f..d0fb628e3 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 @@ -47,6 +47,7 @@ import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.ui.widget.HtmlTextView; +import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -109,14 +110,14 @@ public class LoginActivity extends AccountAuthenticatorActivity { usernameEdit.addTextChangedListener(textWatcher); usernameEdit.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - hideKeyboard(v); + ViewUtil.hideKeyboard(v); } }); passwordEdit.addTextChangedListener(textWatcher); passwordEdit.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - hideKeyboard(v); + ViewUtil.hideKeyboard(v); } }); @@ -144,16 +145,6 @@ public class LoginActivity extends AccountAuthenticatorActivity { Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\\")); } - public void hideKeyboard(View view) { - if (view != null) { - InputMethodManager inputMethodManager = (InputMethodManager) this.getSystemService(Activity.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - } - } - - @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java index 101d4b21e..e804189ab 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java @@ -1,7 +1,6 @@ package fr.free.nrw.commons.category; -import android.app.Activity; import android.content.SharedPreferences; import android.os.Bundle; import android.support.v7.app.AlertDialog; @@ -16,7 +15,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; @@ -43,6 +41,7 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.upload.MwVolleyApi; import fr.free.nrw.commons.utils.StringSortingUtils; +import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -116,7 +115,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment { categoriesFilter.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - hideKeyboard(v); + ViewUtil.hideKeyboard(v); } }); @@ -128,16 +127,6 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment { return rootView; } - public void hideKeyboard(View view) { - - if (view != null) { - InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - } - } - @Override public void onDestroyView() { categoriesFilter.removeTextChangedListener(textWatcher); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java index c7f3c39c0..4390bcef4 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java @@ -33,6 +33,7 @@ import dagger.android.support.AndroidSupportInjection; import fr.free.nrw.commons.R; import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.media.MediaDetailPagerFragment; +import fr.free.nrw.commons.utils.ViewUtil; public class MultipleUploadListFragment extends Fragment { @@ -129,7 +130,7 @@ public class MultipleUploadListFragment extends Fragment { // FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next View target = getActivity().getCurrentFocus(); - hideKeyboard(target); + ViewUtil.hideKeyboard(target); } // FIXME: Wrong result type @@ -178,22 +179,13 @@ public class MultipleUploadListFragment extends Fragment { baseTitle.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - hideKeyboard(v); + ViewUtil.hideKeyboard(v); } }); return view; } - public void hideKeyboard(View view) { - if (view != null) { - InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - } - } - @Override public void onDestroyView() { baseTitle.removeTextChangedListener(textWatcher); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java index aea703f5e..77f76fbed 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java @@ -78,6 +78,7 @@ import fr.free.nrw.commons.modifications.TemplateRemoveModifier; import fr.free.nrw.commons.utils.ImageUtils; import fr.free.nrw.commons.mwapi.MediaWikiApi; +import fr.free.nrw.commons.utils.ViewUtil; import timber.log.Timber; @@ -735,7 +736,7 @@ public class ShareActivity if (CurrentAnimator != null) { CurrentAnimator.cancel(); } - hideKeyboard(ShareActivity.this); + ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit)); InputStream input = null; Bitmap scaled = null; try { @@ -905,12 +906,5 @@ public class ShareActivity }); } - public static void hideKeyboard(Activity activity) { - View view = activity.findViewById(R.id.titleEdit | R.id.descEdit); - if (view != null) { - InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java index 56d443d08..a32fb7b42 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java @@ -1,8 +1,6 @@ package fr.free.nrw.commons.upload; import android.annotation.SuppressLint; -import android.app.Activity; - import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; @@ -11,12 +9,10 @@ import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v7.app.AlertDialog; - import android.text.Editable; import android.text.Html; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; - import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -24,7 +20,6 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; @@ -47,9 +42,9 @@ import fr.free.nrw.commons.R; import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.settings.Prefs; +import fr.free.nrw.commons.utils.ViewUtil; import timber.log.Timber; -import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; public class SingleUploadFragment extends CommonsDaggerSupportFragment { @@ -168,13 +163,13 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment { titleEdit.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - hideKeyboard(v); + ViewUtil.hideKeyboard(v); } }); descEdit.setOnFocusChangeListener((v, hasFocus) -> { if(!hasFocus){ - hideKeyboard(v); + ViewUtil.hideKeyboard(v); } }); @@ -183,15 +178,6 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment { return rootView; } - public void hideKeyboard(View view) { - if (view != null) { - InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - } - } - @Override public void onDestroyView() { titleEdit.removeTextChangedListener(textWatcher); @@ -305,7 +291,7 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment { // FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next View target = getActivity().getCurrentFocus(); - hideKeyboard(target); + ViewUtil.hideKeyboard(target); } @NonNull diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java index b4b26746b..1c45e8178 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java @@ -5,6 +5,7 @@ import android.content.Context; import android.support.design.widget.Snackbar; import android.view.Display; import android.view.View; +import android.view.inputmethod.InputMethodManager; import android.widget.Toast; public class ViewUtil { @@ -27,4 +28,14 @@ public class ViewUtil { } } + public static void hideKeyboard(View view){ + if (view != null) { + InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + view.clearFocus(); + if (manager != null) { + manager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + } + } From bcb90db16c89087e6bda308633ff173bebc54111 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 3 May 2018 09:21:58 +0200 Subject: [PATCH 02/12] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-diq/strings.xml | 3 +- app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-gl/strings.xml | 4 ++ app/src/main/res/values-ml/strings.xml | 94 ++++++++++++++++++++++--- app/src/main/res/values-ru/strings.xml | 1 + 6 files changed, 94 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 60339308c..f265e7ebd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -154,6 +154,7 @@ Vermeide urheberrechtlich geschütztes Material, das du im Internet gefunden hast wie Bilder von Postern, Buchcovern etc. Verstanden? Ja! + Kategorien Lade … Keine ausgewählt diff --git a/app/src/main/res/values-diq/strings.xml b/app/src/main/res/values-diq/strings.xml index b88e412e7..2b66709f0 100644 --- a/app/src/main/res/values-diq/strings.xml +++ b/app/src/main/res/values-diq/strings.xml @@ -83,7 +83,7 @@ Peyd rışten bırış (E-posta ra) E-posta eyar nêbi Karıyaye Kategoriyê peyêni - Fına + Anciya bıcerrebne Bıtexelne Ron Lisans @@ -129,4 +129,5 @@ Keye Bar ke Veciyayış + Anciya bıcerrebne diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 759c44920..58aa93860 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -158,6 +158,7 @@ Αποφύγετε προστατευμένο υλικό που βρήκατε από το Internet, καθώς και εικόνες, αφίσες, εξώφυλλα βιβλίων, κλπ. Τι λες, μπορείς; Ναι! + Κατηγορίες Φόρτωση… Καμία επιλεγμένη diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index e7c3a37c2..630311db4 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -90,6 +90,7 @@ Categorías Configuracións Rexistrarse + Imaxes destacadas Acerca de A aplicación Wikimedia Commons é unha aplicación de código aberto creada e mantida polos cesionarios e voluntarios da comunidade de Wikimedia. A Fundación Wikimedia non está involucrada na creación, desenvolvemento ou mantemento da aplicación. Crear unha nova <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencia</a> para informar de problemas e suxestións. @@ -175,6 +176,8 @@ Título do ficheiro multimedia Descrición Aquí vai a descrición do ficheiro multimedia. Potencialmente, pode ser bastante longo, e necesitará agruparse en múltiples liñas. De tódolos xeitos esperamos que se vexa ben. + Autor + O nome de usuario do autor da imaxe destacada vai aquí. Data de suba Licenza Coordenadas @@ -217,6 +220,7 @@ Saír Titorial Notificacións + Destacados Os sitios situados preto non poden visualizarse sen permisos de localización non se atopou descrición Páxina do ficheiro en Commons diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 485e66acc..632cc0265 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -5,15 +5,23 @@ * Santhosh.thottingal --> - വിക്കിമീഡിയ കോമൺസ് + ദൃശ്യരൂപം + സാർവത്രികം + പ്രതികരണം + സ്ഥലം + കോമൺസ് സജ്ജീകരണങ്ങൾ ഉപയോക്തൃനാമം രഹസ്യവാക്ക് + താങ്കളുടെ കോമൺസ് ബീറ്റ അംഗത്വത്തിൽ പ്രവേശിക്കുക പ്രവേശിക്കുക + രഹസ്യവാക്ക് മറന്നോ? + അംഗത്വമെടുക്കുക പ്രവേശിക്കുന്നു ദയവായി കാത്തിരിക്കുക… പ്രവേശനം വിജയകരം! പ്രവേശനം പരാജയപ്പെട്ടു! + പ്രമാണം കണ്ടെത്താനായില്ല. ദയവായി മറ്റൊരു പ്രമാണം നോക്കുക. സാധുതാനിർണ്ണയം പരാജയപ്പെട്ടു! അപ്‌ലോഡ് തുടങ്ങി! %1$s അപ്‌ലോഡ് ചെയ്തിരിക്കുന്നു! @@ -23,27 +31,30 @@ %1$s അപ്‌ലോഡിങ് പൂർത്തിയാക്കുന്നു %1$s അപ്‌ലോഡിങ് പരാജയപ്പെട്ടു കാണാനായി ടാപ് ചെയ്യുക - - 1 പ്രമാണം അപ്‌ലോഡ് ചെയ്യുന്നു + + ഒരു പ്രമാണം അപ്‌ലോഡ് ചെയ്യുന്നു %1$d പ്രമാണങ്ങൾ അപ്‌ലോഡ് ചെയ്യുന്നു - എന്റെ അപ്‌ലോഡുകൾ + എന്റെ സമീപകാല അപ്‌ലോഡുകൾ നിരയായി വെച്ചു പരാജയപ്പെട്ടു %1$d%% പൂർണ്ണം അപ്‌ലോഡ് ചെയ്തുകൊണ്ടിരിക്കുന്നു ചിത്രശാല ചിത്രം എടുക്കുക + സമീപസ്ഥം എന്റെ അപ്‌ലോഡുകൾ പങ്ക് വെയ്ക്കുക ബ്രൗസറിൽ കാണുക തലക്കെട്ട് + ഈ പ്രമാണത്തിന് ഒരു തലക്കെട്ട് നൽകുക. വിവരണം പ്രവേശിക്കാനായില്ല - നെറ്റ്‌വർക്ക് പരാജയപ്പെട്ടു പ്രവേശിക്കാനായില്ല - ദയവായി താങ്കളുടെ ഉപയോക്തൃനാമം പരിശോധിക്കുക പ്രവേശിക്കാനായില്ല - ദയവായി താങ്കളുടെ രഹസ്യവാക്ക് പരിശോധിക്കുക - നിരവധി വിജയകരമല്ലാത്ത ശ്രമങ്ങൾ നടന്നിരിക്കുന്നു. വീണ്ടും ശ്രമിക്കുന്നതിനു മുമ്പ് ഏതാനം മിനിറ്റുകൾ വിശ്രമിക്കുക + നിരവധി വിജയകരമല്ലാത്ത ശ്രമങ്ങൾ നടന്നിരിക്കുന്നു. വീണ്ടും ശ്രമിക്കുന്നതിനു മുമ്പ് ഏതാനം മിനിറ്റുകൾ വിശ്രമിക്കുക. ക്ഷമിക്കുക, ഈ ഉപയോക്താവ് കോമൺസിൽ നിന്ന് തടയപ്പെട്ടിരിക്കുകയാണ് + താങ്കളുടെ ദ്വി-ഘടക സാധൂകരണ കോഡ് നൽകുക. പ്രവേശനം പരാജയപ്പെട്ടു അപ്‌ലോഡ് ഈ ഗണത്തിന് പേരിടുക @@ -51,6 +62,11 @@ അപ്‌ലോഡ് വർഗ്ഗങ്ങളിൽ തിരയുക സേവ് ചെയ്യുക + പുതുക്കുക + പട്ടിക + താങ്കളുടെ ഉപകരണത്തിൽ ജി.പി.എസ്. പ്രവർത്തനരഹിതമാണ്. അത് പ്രവർത്തനസജ്ജമാക്കണോ? + ജി.പി.എസ്. സജ്ജമാക്കുക + ഇതുവരെ അപ്‌ലോഡുകൾ ഒന്നുമില്ല ഒരു അപ്‌ലോഡും ചെയ്തില്ല ഒരു അപ്‌ലോഡ് @@ -68,6 +84,8 @@ താങ്കളുടെ ചിത്രങ്ങൾ വിക്കിമീഡിയ കോമൺസിൽ കൂടുതൽ എളുപ്പത്തിൽ കണ്ടെത്തപ്പെടാനായി വർഗ്ഗങ്ങൾ ചേർക്കുക.\n\nവർഗ്ഗങ്ങൾ ചേർക്കാനായി ടൈപ്പ് ചെയ്ത് തുടങ്ങുക.\nഈ ഘട്ടം ഒഴിവാക്കാൻ ടാപ് ചെയ്യുക (അല്ലെങ്കിൽ പിന്നോട്ട് പോവുക). വർഗ്ഗങ്ങൾ സജ്ജീകരണങ്ങൾ + അംഗത്വമെടുക്കുക + തിരഞ്ഞെടുത്ത ചിത്രങ്ങൾ വിവരണം <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">അപാച്ചേ അനുമതിപത്രം പതിപ്പ് 2</a> പ്രകാരം പുറത്തിറക്കപ്പെട്ട ഓപ്പൺ സോഴ്സ് സോഫ്റ്റ്‌വേർ സ്രോതസ്സ് രൂപം <a href=\"https://github.com/commons-app/apps-android-commons\">ജിറ്റ്ഹബിൽ</a> ലഭ്യമാണ്.\nപ്രശ്നങ്ങൾ <a href=\" https://github.com/commons-app/apps-android-commons/issues\">ബഗ്സില്ലയിൽ</a> അറിയിക്കുക. @@ -81,9 +99,11 @@ റദ്ദാക്കുക ചിത്രം %1$s പ്രകാരം അനുമതി നൽകപ്പെടുന്നതാണ് ഡൗൺലോഡ് - അനുമതി - സി.സി. ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 3.0 - സി.സി. ആട്രിബ്യൂഷൻ 3.0 + സ്വതേയുള്ള ഉപയോഗാനുമതി + ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 4.0 + ആട്രിബ്യൂഷൻ 4.0 + ആട്രിബ്യൂഷൻ-ഷെയർ‌എലൈക് 3.0 + ആട്രിബ്യൂഷൻ 3.0 സി.സി.0 സി.സി. ബൈ-എസ്.എ. 3.0 സി.സി. ബൈ-എസ്.എ. 3.0 (ഓസ്ട്രിയ) @@ -97,6 +117,8 @@ സി.സി. ബൈ-എസ്.എ. 3.0 (പോളണ്ട്) സി.സി. ബൈ-എസ്.എ. 3.0 (റൊമേനിയ) സി.സി. ബൈ 3.0 + സി.സി. ബൈ-എസ്.എ. 4.0 + സി.സി. ബൈ 4.0 സി.സി. സീറോ താങ്കളെടുക്കുന്ന ചിത്രങ്ങൾ സംഭാവന ചെയ്യുക. വിക്കിപീഡിയ ലേഖനങ്ങൾ ജീവസ്സുറ്റതാക്കിത്തീർക്കുക! വിക്കിപീഡിയയിലുള്ള ചിത്രങ്ങൾ വിക്കിമീഡിയ കോമൺസിൽ നിന്നാണ് @@ -105,9 +127,63 @@ മനസ്സിലായോ? ശരി! വർഗ്ഗങ്ങൾ - ശേഖരിക്കുന്നു… + ശേഖരിക്കുന്നു… ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല വിവരണമൊന്നുമില്ല അജ്ഞാതമായ അനുമതി പുതുക്കുക + ശരി + സമീപ സ്ഥലങ്ങൾ + മുന്നറിയിപ്പ് + ഈ പ്രമാണം കോമൺസിൽ നിലവിലുണ്ട്. തുടരണം എന്ന് താങ്കൾക്കുറപ്പാണോ? + അതെ + അല്ല + ശീർഷകം + മീഡിയയുടെ തലക്കെട്ട് + വിവരണം + സ്രഷ്ടാവ് + അപ്‌ലോഡ് ചെയ്ത തീയതി + ഉപയോഗാനുമതി + നിർദ്ദേശാങ്കങ്ങൾ + ഒന്നും നൽകിയിട്ടില്ല + കോമൺസ് ലോഗോ + കോമൺസ് വെബ്‌സൈറ്റ് + പശ്ചാത്തല ചിത്രം + ചിത്രം അപ്‌ലോഡ് ചെയ്യുക + സാവോ പർവ്വതം + ലാമകൾ + മഴവിൽ പാലം + തുലിപ് + സെൽഫികൾ വേണ്ട + പകർപ്പവകാശ സംരക്ഷിത ചിത്രം + സിഡ്നി ഓപെറാ ഹൗസ് + റദ്ദാക്കുക + തുറക്കുക + അടയ്ക്കുക + പ്രധാനം + അപ്‌ലോഡ് + സമീപസ്ഥം + വിവരണം + സജ്ജീകരണങ്ങൾ + പ്രതികരണം + ലോഗൗട്ട് + സഹായം + അറിയിപ്പുകൾ + തിരഞ്ഞെടുക്കപ്പെട്ടത് + വിവരണങ്ങൾ ഒന്നും കണ്ടെത്തിയില്ല + കോമൺസ് പ്രമാണ താൾ + വിക്കിഡേറ്റാ ഇനം + വിക്കിപീഡിയ ലേഖനം + അനുമതി നൽകുക + താങ്കളുടെ അംഗത്വത്തിൽ പ്രവേശിക്കുക + ബ്രൗസറിൽ കാണുക + വിക്കിഡേറ്റാ + വിക്കിപീഡിയ + കോമൺസ് + <u>പതിവുചോദ്യങ്ങൾ</u> + <u>പരിഭാഷപ്പെടുത്തുക</u> + ഭാഷകൾ + തുടരുക + റദ്ദാക്കുക + വീണ്ടും ശ്രമിക്കുക diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c649398bb..4dc90489d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -170,6 +170,7 @@ Избегайте материалов, защищённых авторским правом, например, найденных в Интернете, изображений плакатов, книжных обложек и т.п. Вам это понятно? Да! + * Категории Загрузка… Ничего не выбрано From 693c37f2ece841e6a306113d76d900213f99b22e Mon Sep 17 00:00:00 2001 From: Ujjwal Agrawal Date: Fri, 4 May 2018 15:26:13 +0530 Subject: [PATCH 03/12] Updated share feature (#1474) --- app/src/main/java/fr/free/nrw/commons/AboutActivity.java | 3 ++- app/src/main/res/drawable/ic_share_black_24dp.xml | 4 ++-- app/src/main/res/menu/menu_about.xml | 2 +- app/src/main/res/values/strings.xml | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java index e6bf34736..65cba7531 100644 --- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java @@ -135,9 +135,10 @@ public class AboutActivity extends NavigationBaseActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.share_app_icon: + String shareText = "Upload photos to Wikimedia Commons on your phone\nDownload the Commons app: http://play.google.com/store/apps/details?id=fr.free.nrw.commons"; Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "http://play.google.com/store/apps/details?id=fr.free.nrw.commons"); + sendIntent.putExtra(Intent.EXTRA_TEXT, shareText); sendIntent.setType("text/plain"); startActivity(Intent.createChooser(sendIntent, "Share app via...")); return true; diff --git a/app/src/main/res/drawable/ic_share_black_24dp.xml b/app/src/main/res/drawable/ic_share_black_24dp.xml index 01c81322d..203b1d84c 100644 --- a/app/src/main/res/drawable/ic_share_black_24dp.xml +++ b/app/src/main/res/drawable/ic_share_black_24dp.xml @@ -1,5 +1,5 @@ - + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/menu/menu_about.xml b/app/src/main/res/menu/menu_about.xml index e56fc3af2..fdd7ff6fd 100644 --- a/app/src/main/res/menu/menu_about.xml +++ b/app/src/main/res/menu/menu_about.xml @@ -7,7 +7,7 @@ Proceed Cancel Retry + Share App From 228aa2129012a73fcedfd5271901d7102f9c763f Mon Sep 17 00:00:00 2001 From: Josephine Lim Date: Mon, 7 May 2018 04:05:55 +1000 Subject: [PATCH 04/12] Update contributing.md (#1477) --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f1feeac7..5a8e7af19 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,14 @@ changed. It is also good to prefix the first line with "area: " where the "area" is a filename or identifier for the general area of the code being modified. The body should provide a meaningful commit message. +# Write Javadocs + +We require contributors to include Javadocs for all new methods and classes submitted via PRs (after 1 May 2018). This is aimed at making it easier for new contributors to dive into our codebase, especially those who are new to Android development. A few things to note: + +- This should not replace the need for code that is easily-readable in and of itself +- Please make sure that your Javadocs are reasonably descriptive, not just a copy of the method name +- Please do not use `@author` tags - we aim for collective code ownership, and if needed, GitHub allows us to see who wrote something without needing to add these tags + # Write tests for your code (if possible) # Make sure the Wiki pages don't become stale by updating them (if needed) From 96eae8c0a6dd2550aa9dc11dc9f330e24328438f Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 7 May 2018 07:54:16 +0200 Subject: [PATCH 05/12] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hu/strings.xml | 8 ++++++++ app/src/main/res/values-iw/strings.xml | 1 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-mk/strings.xml | 1 + app/src/main/res/values-pms/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 2 ++ app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values-zh/strings.xml | 1 + 13 files changed, 21 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f265e7ebd..36e013976 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -269,4 +269,5 @@ Fortfahren Abbrechen Erneut versuchen + App teilen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 58aa93860..0dae5c65e 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -273,4 +273,5 @@ Συνέχεια Ακύρωση Ξαναπροσπαθήστε + Κοινοποίηση εφαρμογής diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8494a7a36..442fc6bc5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -279,4 +279,5 @@ Continuer Annuler Réessayer + Partager les applications diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 5524afb0e..81e655b95 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -13,6 +13,7 @@ * ViDam --> + Megjelenés Általános Visszajelzés Helyszín @@ -145,6 +146,7 @@ Híres emberek (a polgármestered, olimpikonok, akikkel találkoztál) Kérjük, NE tölts fel: - Szelfiket vagy képeket a barátaidról\n- Internetröl letöltött képeket\n- Kereskedelmi alkalmazások képernyőképeit + Az Internetről letöltött képek Példa feltöltés: - Cím: Sydney-i Operaház\n- Leírás: A Sydney-i Operaház az öböl túlpartjáról\n- Kategóriák: Sydney Opera House from the west, Sydney Opera House remote views Cím: Sydney-i Operaház @@ -191,6 +193,7 @@ Commons Logo Commons weboldal Commons Facebook-oldal + Commons Github forráskód Háttérkép Nem található kép Kép feltöltése @@ -223,6 +226,8 @@ Hiba a képek gyorsítótárazásakor Egy egyedi, leíró cím a fájlnak, ami fájlnévként fog szolgálni. Egyszerű nyelvezetet használhatsz szóközökkel. Ne tedd bele a kiterjesztést. Kérlek a lehető legteljesebb módon írd le a fájlt: hol készült, mit ábrázol, mi a kontextus? Kérlek add meg az objektumokat vagy személyeket a képen, valamint a nehezen kitalálható információkat (például a kép készítésének dátumát, ha az egy tájkép). Amennyiben a média valami szokatlant ábrázol, kérlek fejtsd ki, hogy mi teszi szokatlanná. + Ez a fénykép túl sötét, biztos fel akarod tölteni? A Wikimédia Commons csak enciklopédikus értékkel bíró képeket tart meg. + Ez a fénykép homályos, biztos fel akarod tölteni? A Wikimédia Commons csak enciklopédikus értékkel bíró képeket tart meg. Engedély adása Külső tárhely használata Az alkalmazáson belüli kamerával készült képek mentése az eszközre @@ -238,6 +243,7 @@ A hely nem érhető el. Közeli helyek listájának megtekintéséhez engedély szükséges Üdvözlünk a Wikimedia Commonson, %1$s! Örülünk, hogy itt vagy. + %1$s üzenetet hagyott a vitalapodon Köszönjük a szerkesztésedet! WIKIDATA WIKIPÉDIA @@ -249,6 +255,8 @@ Internet elérhető Nincs értesítés Nyelvek + Folytatás Mégse Újra + Alkalmazás megosztása diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 48750daf9..762a3be31 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -273,4 +273,5 @@ המשך ביטול לנסות שוב + שיתוף היישום diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 11faee9c6..d0e1c21ed 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -266,4 +266,5 @@ 진행 취소 다시 시도 + 앱 공유 diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index dd1e6f03e..4e44dad60 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -263,4 +263,5 @@ Продолжи Откажи Пробај пак + Сподели прилог diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 0cd748031..30115f1ad 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -263,4 +263,5 @@ Andé anans Anulé Prové torna + Partagé j\'aplicassion diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4bc478527..6722ae4bb 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -275,4 +275,5 @@ Avançar Cancelar Tentar novamente + Compartilhar o aplicativo diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5e4862ecb..b0717f3dc 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -159,6 +159,7 @@ Evite materiais protegidos por direitos de autor que tenham sido encontrados na Internet, bem como imagens de cartazes, capas de livros, etc. Acha que conseguiu? Sim! + Categorias A carregar… Nenhuma selecionada diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index fa5174ced..24ef60b61 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -156,6 +156,7 @@ Undvik upphovsrättsskyddat material som du hittar på Internet, samt bilder av affischer, bokomslag, etc. Tror du att du förstår? Ja! + Kategorier Läser in… Ingen markerad @@ -270,4 +271,5 @@ Fortsätt Avbryt Försök igen + Dela app diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 4966d9c08..a2878cddf 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -271,4 +271,5 @@ 已進行 取消 重試 + 分享應用程式 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 83650b408..d3d049ea2 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -269,4 +269,5 @@ 已处理 取消 重试 + 分享应用 From 9845a6265d7a1339998413fe1770931264a1af34 Mon Sep 17 00:00:00 2001 From: Vivek Maskara Date: Mon, 7 May 2018 13:39:23 +0530 Subject: [PATCH 06/12] Integrate API for displaying featured images (#1456) * Integrate API for displaying featured images * Add pagination and refactor code so that it can be reused for category images * Add license info to the images * Fix author view * Remove unused values * Fix minor issues with featured images * Fix null license url issue * Remove some log lines * Fix back navigation issue * fix tests * fix test inits * Gracefully handling various error situations * Added java docs --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 5 +- .../category/CategoryImageController.java | 29 +++ .../commons/category/CategoryImageUtils.java | 225 +++++++++++++++++ .../category/CategoryImagesActivity.java | 160 ++++++++++++ .../category/CategoryImagesListFragment.java | 227 ++++++++++++++++++ .../nrw/commons/category/GridViewAdapter.java | 88 +++++++ .../nrw/commons/category/QueryContinue.java | 24 ++ .../nrw/commons/di/ActivityBuilderModule.java | 4 +- .../commons/di/CommonsApplicationModule.java | 30 ++- .../nrw/commons/di/FragmentBuilderModule.java | 4 +- .../nrw/commons/featured/FeaturedImage.java | 44 ---- .../featured/FeaturedImagesActivity.java | 114 --------- .../featured/FeaturedImagesListFragment.java | 52 ---- .../commons/featured/MockGridViewAdapter.java | 50 ---- .../commons/media/MediaDetailFragment.java | 31 ++- .../mwapi/ApacheHttpClientMediaWikiApi.java | 98 +++++++- .../free/nrw/commons/mwapi/MediaWikiApi.java | 3 + .../commons/theme/NavigationBaseActivity.java | 7 +- .../free/nrw/commons/utils/ContinueUtils.java | 15 ++ ...mages.xml => activity_category_images.xml} | 11 +- ...mages.xml => fragment_category_images.xml} | 15 +- ..._images.xml => layout_category_images.xml} | 10 +- app/src/main/res/values/strings.xml | 4 + .../nrw/commons/TestCommonsApplication.kt | 5 +- .../mwapi/ApacheHttpClientMediaWikiApiTest.kt | 5 +- 26 files changed, 953 insertions(+), 309 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/category/CategoryImageController.java create mode 100644 app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java create mode 100644 app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java create mode 100644 app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java create mode 100644 app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java create mode 100644 app/src/main/java/fr/free/nrw/commons/category/QueryContinue.java delete mode 100644 app/src/main/java/fr/free/nrw/commons/featured/FeaturedImage.java delete mode 100644 app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesActivity.java delete mode 100644 app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesListFragment.java delete mode 100644 app/src/main/java/fr/free/nrw/commons/featured/MockGridViewAdapter.java create mode 100644 app/src/main/java/fr/free/nrw/commons/utils/ContinueUtils.java rename app/src/main/res/layout/{activity_featured_images.xml => activity_category_images.xml} (69%) rename app/src/main/res/layout/{fragment_featured_images.xml => fragment_category_images.xml} (70%) rename app/src/main/res/layout/{layout_featured_images.xml => layout_category_images.xml} (88%) diff --git a/app/build.gradle b/app/build.gradle index 535f58143..9c6f62fd4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,6 +49,8 @@ dependencies { implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0' implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0' + implementation 'org.jsoup:jsoup:1.11.3' + implementation 'com.facebook.fresco:fresco:1.5.0' implementation 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6aab09b55..17f6770d2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,8 +92,9 @@ android:label="@string/navigation_item_notification" /> + android:name=".category.CategoryImagesActivity" + android:label="@string/title_activity_featured_images" + android:parentActivityName=".contributions.ContributionsActivity" /> diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImageController.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImageController.java new file mode 100644 index 000000000..3495d710c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImageController.java @@ -0,0 +1,29 @@ +package fr.free.nrw.commons.category; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import fr.free.nrw.commons.Media; +import fr.free.nrw.commons.mwapi.MediaWikiApi; + +@Singleton +public class CategoryImageController { + + private MediaWikiApi mediaWikiApi; + + @Inject + public CategoryImageController(MediaWikiApi mediaWikiApi) { + this.mediaWikiApi = mediaWikiApi; + } + + /** + * Takes a category name as input and calls the API to get a list of images for that category + * @param categoryName + * @return + */ + public List getCategoryImages(String categoryName) { + return mediaWikiApi.getCategoryImages(categoryName); + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java new file mode 100644 index 000000000..18749847e --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImageUtils.java @@ -0,0 +1,225 @@ +package fr.free.nrw.commons.category; + +import org.jsoup.Jsoup; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.annotation.Nullable; + +import fr.free.nrw.commons.Media; +import timber.log.Timber; + +public class CategoryImageUtils { + + /** + * The method iterates over the child nodes to return a list of Media objects + * @param childNodes + * @return + */ + public static List getMediaList(NodeList childNodes) { + List categoryImages = new ArrayList<>(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + categoryImages.add(getMediaFromPage(node)); + } + + return categoryImages; + } + + /** + * Creates a new Media object from the XML response as received by the API + * @param node + * @return + */ + private static Media getMediaFromPage(Node node) { + Media media = new Media(null, + getImageUrl(node), + getFileName(node), + getDescription(node), + getDataLength(node), + getDateCreated(node), + getDateCreated(node), + getCreator(node) + ); + + media.setLicense(getLicense(node)); + + return media; + } + + /** + * Extracts the filename of the uploaded image + * @param document + * @return + */ + private static String getFileName(Node document) { + Element element = (Element) document; + return element.getAttribute("title"); + } + + /** + * Extracts the image description for that particular upload + * @param document + * @return + */ + private static String getDescription(Node document) { + return getMetaDataValue(document, "ImageDescription"); + } + + /** + * Extracts license information from the image meta data + * @param document + * @return + */ + private static String getLicense(Node document) { + return getMetaDataValue(document, "License"); + } + + /** + * Returns the parsed value of artist from the response + * The artist information is returned as a HTML string from the API. Jsoup library parses the HTML string + * to extract just the text value + * @param document + * @return + */ + private static String getCreator(Node document) { + String artist = getMetaDataValue(document, "Artist"); + if (artist != null) { + return Jsoup.parse(artist).text(); + } + return null; + } + + /** + * Returns the parsed date of creation of the image + * @param document + * @return + */ + private static Date getDateCreated(Node document) { + String dateTime = getMetaDataValue(document, "DateTime"); + if (dateTime != null && !dateTime.equals("")) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + return format.parse(dateTime); + } catch (ParseException e) { + Timber.d("Error occurred while parsing date %s", dateTime); + return new Date(); + } + } + return new Date(); + } + + /** + * @param document + * @return Returns the url attribute from the imageInfo node + */ + private static String getImageUrl(Node document) { + Element element = (Element) getImageInfo(document); + if (element != null) { + return element.getAttribute("url"); + } + return null; + } + + /** + * Takes the node document and gives out the attribute length from the node document + * @param document + * @return + */ + private static long getDataLength(Node document) { + Element element = (Element) document; + if (element != null) { + String length = element.getAttribute("length"); + if (length != null && !length.equals("")) { + return Long.parseLong(length); + } + } + return 0L; + } + + /** + * Generic method to get the value of any meta as returned by the getMetaData function + * @param document node document as returned by API + * @param metaName the name of meta node to be returned + * @return + */ + private static String getMetaDataValue(Node document, String metaName) { + Element metaData = getMetaData(document, metaName); + if (metaData != null) { + return metaData.getAttribute("value"); + } + return null; + } + + /** + * Generic method to return an element taking the node document and metaName as input + * @param document node document as returned by API + * @param metaName the name of meta node to be returned + * @return + */ + @Nullable + private static Element getMetaData(Node document, String metaName) { + Node extraMetaData = getExtraMetaData(document); + if (extraMetaData != null) { + Node node = getNode(extraMetaData, metaName); + if (node != null) { + return (Element) node; + } + } + return null; + } + + /** + * Extracts extmetadata from the response XML + * @param document + * @return + */ + @Nullable + private static Node getExtraMetaData(Node document) { + Node imageInfo = getImageInfo(document); + if (imageInfo != null) { + return getNode(imageInfo, "extmetadata"); + } + return null; + } + + /** + * Extracts the ii node from the imageinfo node + * @param document + * @return + */ + @Nullable + private static Node getImageInfo(Node document) { + Node imageInfo = getNode(document, "imageinfo"); + if (imageInfo != null) { + return getNode(imageInfo, "ii"); + } + return null; + } + + /** + * Takes a parent node as input and returns a child node if present + * @param node parent node + * @param nodeName child node name + * @return + */ + @Nullable + public static Node getNode(Node node, String nodeName) { + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node nodeItem = childNodes.item(i); + Element item = (Element) nodeItem; + if (item.getTagName().equals(nodeName)) { + return nodeItem; + } + } + return null; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java new file mode 100644 index 000000000..1f385b258 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java @@ -0,0 +1,160 @@ +package fr.free.nrw.commons.category; + +import android.content.Context; +import android.content.Intent; +import android.database.DataSetObserver; +import android.os.Bundle; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.widget.AdapterView; + +import butterknife.ButterKnife; +import fr.free.nrw.commons.Media; +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.auth.AuthenticatedActivity; +import fr.free.nrw.commons.media.MediaDetailPagerFragment; +import timber.log.Timber; + +/** + * This activity displays pictures of a particular category + * Its generic and simply takes the name of category name in its start intent to load all images in + * a particular category. This activity is currently being used to display a list of featured images, + * which is nothing but another category on wikimedia commons. + */ + +public class CategoryImagesActivity + extends AuthenticatedActivity + implements FragmentManager.OnBackStackChangedListener, + MediaDetailPagerFragment.MediaDetailProvider, + AdapterView.OnItemClickListener{ + + + private FragmentManager supportFragmentManager; + private CategoryImagesListFragment categoryImagesListFragment; + private MediaDetailPagerFragment mediaDetails; + + @Override + protected void onAuthCookieAcquired(String authCookie) { + + } + + @Override + protected void onAuthFailure() { + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_category_images); + ButterKnife.bind(this); + + // Activity can call methods in the fragment by acquiring a + // reference to the Fragment from FragmentManager, using findFragmentById() + supportFragmentManager = getSupportFragmentManager(); + setCategoryImagesFragment(); + supportFragmentManager.addOnBackStackChangedListener(this); + if (savedInstanceState != null) { + mediaDetails = (MediaDetailPagerFragment) supportFragmentManager + .findFragmentById(R.id.fragmentContainer); + + } + requestAuthToken(); + initDrawer(); + setPageTitle(); + } + + /** + * Gets the categoryName from the intent and initializes the fragment for showing images of that category + */ + private void setCategoryImagesFragment() { + categoryImagesListFragment = new CategoryImagesListFragment(); + String categoryName = getIntent().getStringExtra("categoryName"); + if (getIntent() != null && categoryName != null) { + Bundle arguments = new Bundle(); + arguments.putString("categoryName", categoryName); + categoryImagesListFragment.setArguments(arguments); + FragmentTransaction transaction = supportFragmentManager.beginTransaction(); + transaction + .add(R.id.fragmentContainer, categoryImagesListFragment) + .commit(); + } + } + + /** + * Gets the passed title from the intents and displays it as the page title + */ + private void setPageTitle() { + if (getIntent() != null && getIntent().getStringExtra("title") != null) { + setTitle(getIntent().getStringExtra("title")); + } + } + + @Override + public void onBackStackChanged() { + } + + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + if (mediaDetails == null || !mediaDetails.isVisible()) { + // set isFeaturedImage true for featured images, to include author field on media detail + mediaDetails = new MediaDetailPagerFragment(false, true); + FragmentManager supportFragmentManager = getSupportFragmentManager(); + supportFragmentManager + .beginTransaction() + .replace(R.id.fragmentContainer, mediaDetails) + .addToBackStack(null) + .commit(); + supportFragmentManager.executePendingTransactions(); + } + mediaDetails.showImage(i); + } + + /** + * Consumers should be simply using this method to use this activity. + * @param context + * @param title Page title + * @param categoryName Name of the category for displaying its images + */ + public static void startYourself(Context context, String title, String categoryName) { + Intent intent = new Intent(context, CategoryImagesActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + intent.putExtra("title", title); + intent.putExtra("categoryName", categoryName); + context.startActivity(intent); + } + + @Override + public Media getMediaAtPosition(int i) { + if (categoryImagesListFragment.getAdapter() == null) { + // not yet ready to return data + return null; + } else { + return (Media) categoryImagesListFragment.getAdapter().getItem(i); + } + } + + @Override + public int getTotalMediaCount() { + if (categoryImagesListFragment.getAdapter() == null) { + return 0; + } + return categoryImagesListFragment.getAdapter().getCount(); + } + + @Override + public void notifyDatasetChanged() { + + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java new file mode 100644 index 000000000..3b6734edd --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesListFragment.java @@ -0,0 +1,227 @@ +package fr.free.nrw.commons.category; + +import android.annotation.SuppressLint; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.GridView; +import android.widget.ListAdapter; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import dagger.android.support.DaggerFragment; +import fr.free.nrw.commons.Media; +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.utils.NetworkUtils; +import fr.free.nrw.commons.utils.ViewUtil; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +/** + * Displays images for a particular category with load more on scrolling incorporated + */ +public class CategoryImagesListFragment extends DaggerFragment { + + private static int TIMEOUT_SECONDS = 15; + + private GridViewAdapter gridAdapter; + + @BindView(R.id.statusMessage) + TextView statusTextView; + @BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar; + @BindView(R.id.categoryImagesList) GridView gridView; + + private boolean hasMoreImages = true; + private boolean isLoading; + private String categoryName = null; + + @Inject CategoryImageController controller; + @Inject @Named("category_prefs") SharedPreferences categoryPreferences; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_category_images, container, false); + ButterKnife.bind(this, v); + return v; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity()); + initViews(); + } + + /** + * Initializes the UI elements for the fragment + * Setup the grid view to and scroll listener for it + */ + private void initViews() { + String categoryName = getArguments().getString("categoryName"); + if (getArguments() != null && categoryName != null) { + this.categoryName = categoryName; + resetQueryContinueValues(categoryName); + initList(); + setScrollListener(); + } + } + + /** + * Query continue values determine the last page that was loaded for the particular keyword + * This method resets those values, so that the results can be queried from the first page itself + * @param keyword + */ + private void resetQueryContinueValues(String keyword) { + SharedPreferences.Editor editor = categoryPreferences.edit(); + editor.remove(keyword); + editor.apply(); + } + + /** + * Checks for internet connection and then initializes the grid view with first 10 images of that category + */ + @SuppressLint("CheckResult") + private void initList() { + if(!NetworkUtils.isInternetConnectionEstablished(getContext())) { + handleNoInternet(); + return; + } + + isLoading = true; + progressBar.setVisibility(VISIBLE); + Observable.fromCallable(() -> controller.getCategoryImages(categoryName)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) + .subscribe(this::handleSuccess, this::handleError); + } + + /** + * Handles the UI updates for no internet scenario + */ + private void handleNoInternet() { + progressBar.setVisibility(GONE); + if (gridAdapter == null || gridAdapter.isEmpty()) { + statusTextView.setVisibility(VISIBLE); + statusTextView.setText(getString(R.string.no_internet)); + } else { + ViewUtil.showSnackbar(gridView, R.string.no_internet); + } + } + + /** + * Logs and handles API error scenario + * @param throwable + */ + private void handleError(Throwable throwable) { + Timber.e(throwable, "Error occurred while loading featured images"); + initErrorView(); + } + + /** + * Handles the UI updates for a error scenario + */ + private void initErrorView() { + ViewUtil.showSnackbar(gridView, R.string.error_loading_images); + progressBar.setVisibility(GONE); + if (gridAdapter == null || gridAdapter.isEmpty()) { + statusTextView.setVisibility(VISIBLE); + statusTextView.setText(getString(R.string.no_images_found)); + } else { + statusTextView.setVisibility(GONE); + } + } + + /** + * Initializes the adapter with a list of Media objects + * @param mediaList + */ + private void setAdapter(List mediaList) { + gridAdapter = new GridViewAdapter(this.getContext(), R.layout.layout_category_images, mediaList); + gridView.setAdapter(gridAdapter); + } + + /** + * Sets the scroll listener for the grid view so that more images are fetched when the user scrolls down + * Checks if the category has more images before loading + * Also checks whether images are currently being fetched before triggering another request + */ + private void setScrollListener() { + gridView.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (hasMoreImages && !isLoading && (firstVisibleItem + visibleItemCount + 1 >= totalItemCount)) { + isLoading = true; + fetchMoreImages(); + } + } + }); + } + + /** + * Fetches more images for the category and adds it to the grid view adapter + */ + @SuppressLint("CheckResult") + private void fetchMoreImages() { + if(!NetworkUtils.isInternetConnectionEstablished(getContext())) { + handleNoInternet(); + return; + } + + progressBar.setVisibility(VISIBLE); + Observable.fromCallable(() -> controller.getCategoryImages(categoryName)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) + .subscribe(this::handleSuccess, this::handleError); + } + + /** + * Handles the success scenario + * On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter + * @param collection + */ + private void handleSuccess(List collection) { + if(collection == null || collection.isEmpty()) { + initErrorView(); + hasMoreImages = false; + return; + } + + if(gridAdapter == null) { + setAdapter(collection); + } else { + gridAdapter.addItems(collection); + } + + progressBar.setVisibility(GONE); + isLoading = false; + statusTextView.setVisibility(GONE); + } + + public ListAdapter getAdapter() { + return gridView.getAdapter(); + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java new file mode 100644 index 000000000..c8e6066f6 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java @@ -0,0 +1,88 @@ +package fr.free.nrw.commons.category; + +import android.app.Activity; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import fr.free.nrw.commons.Media; +import fr.free.nrw.commons.MediaWikiImageView; +import fr.free.nrw.commons.R; + +/** + * This is created to only display UI implementation. Needs to be changed in real implementation + */ + +public class GridViewAdapter extends ArrayAdapter { + private Context context; + private List data; + + public GridViewAdapter(Context context, int layoutResourceId, List data) { + super(context, layoutResourceId, data); + this.context = context; + this.data = data; + } + + /** + * Adds more item to the list + * Its triggered on scrolling down in the list + * @param images + */ + public void addItems(List images) { + if (data == null) { + data = new ArrayList<>(); + } + data.addAll(images); + notifyDataSetChanged(); + } + + @Override + public boolean isEmpty() { + return data == null || data.isEmpty(); + } + + /** + * Sets up the UI for the category image item + * @param position + * @param convertView + * @param parent + * @return + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + if (convertView == null) { + LayoutInflater inflater = ((Activity) context).getLayoutInflater(); + convertView = inflater.inflate(R.layout.layout_category_images, null); + } + + Media item = data.get(position); + MediaWikiImageView imageView = convertView.findViewById(R.id.categoryImageView); + TextView fileName = convertView.findViewById(R.id.categoryImageTitle); + TextView author = convertView.findViewById(R.id.categoryImageAuthor); + fileName.setText(item.getFilename()); + setAuthorView(item, author); + imageView.setMedia(item); + return convertView; + } + + /** + * Shows author information if its present + * @param item + * @param author + */ + private void setAuthorView(Media item, TextView author) { + if (item.getCreator() != null && !item.getCreator().equals("")) { + String uploadedByTemplate = context.getString(R.string.image_uploaded_by); + author.setText(String.format(uploadedByTemplate, item.getCreator())); + } else { + author.setVisibility(View.GONE); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/category/QueryContinue.java b/app/src/main/java/fr/free/nrw/commons/category/QueryContinue.java new file mode 100644 index 000000000..e12d5a778 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/QueryContinue.java @@ -0,0 +1,24 @@ +package fr.free.nrw.commons.category; + +/** + * For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages + * https://www.mediawiki.org/wiki/API:Raw_query_continue + */ +public class QueryContinue { + private String continueParam; + private String gcmContinueParam; + + public QueryContinue(String continueParam, String gcmContinueParam) { + this.continueParam = continueParam; + this.gcmContinueParam = gcmContinueParam; + } + + public String getGcmContinueParam() { + return gcmContinueParam; + } + + public String getContinueParam() { + return continueParam; + } +} + 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 f88f3b34a..51aa85903 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 @@ -7,7 +7,7 @@ import fr.free.nrw.commons.WelcomeActivity; 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.featured.FeaturedImagesActivity; +import fr.free.nrw.commons.category.CategoryImagesActivity; import fr.free.nrw.commons.nearby.NearbyActivity; import fr.free.nrw.commons.notification.NotificationActivity; import fr.free.nrw.commons.settings.SettingsActivity; @@ -49,5 +49,5 @@ public abstract class ActivityBuilderModule { abstract NotificationActivity bindNotificationActivity(); @ContributesAndroidInjector - abstract FeaturedImagesActivity bindFeaturedImagesActivity(); + abstract CategoryImagesActivity bindFeaturedImagesActivity(); } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java index e8b915c7e..55281be7e 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java @@ -6,6 +6,8 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.v4.util.LruCache; +import com.google.gson.Gson; + import javax.inject.Named; import javax.inject.Singleton; @@ -85,6 +87,17 @@ public class CommonsApplicationModule { return context.getSharedPreferences("prefs", MODE_PRIVATE); } + /** + * + * @param context + * @return returns categoryPrefs + */ + @Provides + @Named("category_prefs") + public SharedPreferences providesCategorySharedPreferences(Context context) { + return context.getSharedPreferences("categoryPrefs", MODE_PRIVATE); + } + @Provides @Named("direct_nearby_upload_prefs") public SharedPreferences providesDirectNearbyUploadPreferences(Context context) { @@ -106,8 +119,11 @@ public class CommonsApplicationModule { @Provides @Singleton - public MediaWikiApi provideMediaWikiApi(Context context, @Named("default_preferences") SharedPreferences sharedPreferences) { - return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, sharedPreferences); + public MediaWikiApi provideMediaWikiApi(Context context, + @Named("default_preferences") SharedPreferences defaultPreferences, + @Named("category_prefs") SharedPreferences categoryPrefs, + Gson gson) { + return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, defaultPreferences, categoryPrefs, gson); } @Provides @@ -116,6 +132,16 @@ public class CommonsApplicationModule { return new LocationServiceManager(context); } + /** + * Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere. + * @return returns a singleton Gson instance + */ + @Provides + @Singleton + public Gson provideGson() { + return new Gson(); + } + @Provides @Singleton public CacheController provideCacheController() { diff --git a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java index c5cdcb5a7..dfed64871 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java @@ -4,7 +4,7 @@ import dagger.Module; import dagger.android.ContributesAndroidInjector; import fr.free.nrw.commons.category.CategorizationFragment; import fr.free.nrw.commons.contributions.ContributionsListFragment; -import fr.free.nrw.commons.featured.FeaturedImagesListFragment; +import fr.free.nrw.commons.category.CategoryImagesListFragment; import fr.free.nrw.commons.media.MediaDetailFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.nearby.NearbyListFragment; @@ -49,6 +49,6 @@ public abstract class FragmentBuilderModule { abstract SingleUploadFragment bindSingleUploadFragment(); @ContributesAndroidInjector - abstract FeaturedImagesListFragment bindFeaturedImagesListFragment(); + abstract CategoryImagesListFragment bindFeaturedImagesListFragment(); } diff --git a/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImage.java b/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImage.java deleted file mode 100644 index 853fba29e..000000000 --- a/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImage.java +++ /dev/null @@ -1,44 +0,0 @@ -package fr.free.nrw.commons.featured; - - -import fr.free.nrw.commons.Media; - -/** - * Object to hold FeaturedImage - */ - -public class FeaturedImage { - private Media image; - private String author; - private String fileName; - - public FeaturedImage(Media image, String author, String fileName) { - this.image = image; - this.author = author; - this.fileName = fileName; - } - - public Media getImage() { - return image; - } - - public void setImage(Media image) { - this.image = image; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesActivity.java b/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesActivity.java deleted file mode 100644 index a2dc7b00b..000000000 --- a/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesActivity.java +++ /dev/null @@ -1,114 +0,0 @@ -package fr.free.nrw.commons.featured; - -import android.database.DataSetObserver; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.view.View; -import android.widget.AdapterView; - -import butterknife.ButterKnife; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.auth.AuthenticatedActivity; -import fr.free.nrw.commons.media.MediaDetailPagerFragment; - -/** - * This activity displays pic of the days of last xx days - */ - -public class FeaturedImagesActivity - extends AuthenticatedActivity - implements FragmentManager.OnBackStackChangedListener, - MediaDetailPagerFragment.MediaDetailProvider, - AdapterView.OnItemClickListener{ - - private FeaturedImagesListFragment featuredImagesListFragment; - private MediaDetailPagerFragment mediaDetails; - - @Override - protected void onAuthCookieAcquired(String authCookie) { - - } - - @Override - protected void onAuthFailure() { - - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_featured_images); - ButterKnife.bind(this); - - // Activity can call methods in the fragment by acquiring a - // reference to the Fragment from FragmentManager, using findFragmentById() - FragmentManager supportFragmentManager = getSupportFragmentManager(); - featuredImagesListFragment = (FeaturedImagesListFragment)supportFragmentManager - .findFragmentById(R.id.featuedListFragment); - - supportFragmentManager.addOnBackStackChangedListener(this); - if (savedInstanceState != null) { - mediaDetails = (MediaDetailPagerFragment)supportFragmentManager - .findFragmentById(R.id.featuredFragmentContainer); - - } - requestAuthToken(); - initDrawer(); - setTitle(getString(R.string.title_activity_featured_images)); - } - - @Override - public void onBackStackChanged() { - - } - - @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - if (mediaDetails == null || !mediaDetails.isVisible()) { - // set isFeaturedImage true for featured images, to include author field on media detail - mediaDetails = new MediaDetailPagerFragment(false, true); - FragmentManager supportFragmentManager = getSupportFragmentManager(); - supportFragmentManager - .beginTransaction() - .replace(R.id.featuredFragmentContainer, mediaDetails) - .addToBackStack(null) - .commit(); - supportFragmentManager.executePendingTransactions(); - } - mediaDetails.showImage(i); - } - - @Override - public Media getMediaAtPosition(int i) { - if (featuredImagesListFragment.getAdapter() == null) { - // not yet ready to return data - return null; - } else { - return ((FeaturedImage)featuredImagesListFragment.getAdapter().getItem(i)).getImage(); - } - } - - @Override - public int getTotalMediaCount() { - if (featuredImagesListFragment.getAdapter() == null) { - return 0; - } - return featuredImagesListFragment.getAdapter().getCount(); - } - - @Override - public void notifyDatasetChanged() { - - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesListFragment.java b/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesListFragment.java deleted file mode 100644 index 19e33b0ee..000000000 --- a/app/src/main/java/fr/free/nrw/commons/featured/FeaturedImagesListFragment.java +++ /dev/null @@ -1,52 +0,0 @@ -package fr.free.nrw.commons.featured; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.GridView; -import android.widget.ListAdapter; - -import java.util.ArrayList; - -import butterknife.ButterKnife; -import dagger.android.support.DaggerFragment; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; - -public class FeaturedImagesListFragment extends DaggerFragment { - private GridView gridView; - private MockGridViewAdapter gridAdapter; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.fragment_featured_images, container, false); - ButterKnife.bind(this, v); - return v; - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - gridView = getView().findViewById(R.id.featuredImagesList); - gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity()); - gridAdapter = new MockGridViewAdapter(this.getContext(), R.layout.layout_featured_images, getMockFeaturedImages()); - gridView.setAdapter(gridAdapter); - - } - - private ArrayList getMockFeaturedImages(){ - ArrayList featuredImages = new ArrayList<>(); - for (int i=0; i<10; i++){ - featuredImages.add(new FeaturedImage(new Media("test.jpg"), "username: test", "test file name")); - } - return featuredImages; - } - - public ListAdapter getAdapter() { - return gridView.getAdapter(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/featured/MockGridViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/featured/MockGridViewAdapter.java deleted file mode 100644 index 7aa2a8892..000000000 --- a/app/src/main/java/fr/free/nrw/commons/featured/MockGridViewAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -package fr.free.nrw.commons.featured; - -import android.app.Activity; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -import java.util.ArrayList; - -import fr.free.nrw.commons.MediaWikiImageView; -import fr.free.nrw.commons.R; - -/** - * This is created to only display UI implementation. Needs to be changed in real implementation - */ - -public class MockGridViewAdapter extends ArrayAdapter { - private Context context; - private int layoutResourceId; - private ArrayList data = new ArrayList(); - - public MockGridViewAdapter(Context context, int layoutResourceId, ArrayList data) { - super(context, layoutResourceId, data); - this.layoutResourceId = layoutResourceId; - this.context = context; - this.data = data; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - if (convertView == null) { - LayoutInflater inflater = ((Activity) context).getLayoutInflater(); - convertView = inflater.inflate(R.layout.layout_featured_images, null); - } - - FeaturedImage item = data.get(position); - MediaWikiImageView imageView = convertView.findViewById(R.id.featuredImageView); - TextView fileName = convertView.findViewById(R.id.featuredImageTitle); - TextView author = convertView.findViewById(R.id.featuredImageAuthor); - fileName.setText("Test file name"); - author.setText("Uploaded by: Test user name"); - imageView.setMedia(item.getImage()); - return convertView; - } - -} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index 037f92e56..c3a977ab5 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -45,6 +45,7 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.ui.widget.CompatTextView; import timber.log.Timber; +import static android.view.View.*; import static android.widget.Toast.LENGTH_SHORT; public class MediaDetailFragment extends CommonsDaggerSupportFragment { @@ -154,9 +155,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { authorLayout = (LinearLayout) view.findViewById(R.id.authorLinearLayout); if (isFeaturedMedia){ - authorLayout.setVisibility(View.VISIBLE); + authorLayout.setVisibility(VISIBLE); } else { - authorLayout.setVisibility(View.GONE); + authorLayout.setVisibility(GONE); } licenseList = new LicenseList(getActivity()); @@ -306,6 +307,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { } rebuildCatList(); + if(media.getCreator() == null || media.getCreator().equals("")) { + authorLayout.setVisibility(GONE); + } else { + author.setText(media.getCreator()); + } + checkDeletion(media); } @@ -313,13 +320,17 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { if (licenseLink(media) != null) { license.setOnClickListener(v -> openWebBrowser(licenseLink(media))); } else { - Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT); - toast.show(); + if(isFeaturedMedia) { + Timber.d("Unable to fetch license URL for %s", media.getLicense()); + } else { + Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT); + toast.show(); + } } if (media.getCoordinates() != null) { coordinates.setOnClickListener(v -> openMap(media.getCoordinates())); } - if (delete.getVisibility() == View.VISIBLE) { + if (delete.getVisibility() == VISIBLE) { enableDeleteButton(true); delete.setOnClickListener(v -> { @@ -369,7 +380,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); }); } - if (nominatedforDeletion.getVisibility() == View.VISIBLE){ + if (nominatedforDeletion.getVisibility() == VISIBLE){ seeMore.setOnClickListener(v -> { openWebBrowser(media.getFilePageTitle().getMobileUri().toString()); }); @@ -476,12 +487,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { private void checkDeletion(Media media){ if (media.getRequestedDeletion()){ - delete.setVisibility(View.GONE); - nominatedforDeletion.setVisibility(View.VISIBLE); + delete.setVisibility(GONE); + nominatedforDeletion.setVisibility(VISIBLE); } else{ - delete.setVisibility(View.VISIBLE); - nominatedforDeletion.setVisibility(View.GONE); + delete.setVisibility(VISIBLE); + nominatedforDeletion.setVisibility(GONE); } } 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 78051abd8..6629d0933 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 @@ -9,6 +9,8 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.Log; +import com.google.gson.Gson; + import org.apache.http.HttpResponse; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; @@ -38,7 +40,10 @@ import java.util.Locale; import java.util.concurrent.Callable; import fr.free.nrw.commons.BuildConfig; +import fr.free.nrw.commons.Media; import fr.free.nrw.commons.PageTitle; +import fr.free.nrw.commons.category.CategoryImageUtils; +import fr.free.nrw.commons.category.QueryContinue; import fr.free.nrw.commons.notification.Notification; import fr.free.nrw.commons.notification.NotificationUtils; import in.yuvi.http.fluent.Http; @@ -46,6 +51,8 @@ import io.reactivex.Observable; import io.reactivex.Single; import timber.log.Timber; +import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue; + /** * @author Addshore */ @@ -56,9 +63,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { private AbstractHttpClient httpClient; private MWApi api; private Context context; - private SharedPreferences sharedPreferences; + private SharedPreferences defaultPreferences; + private SharedPreferences categoryPreferences; + private Gson gson; - public ApacheHttpClientMediaWikiApi(Context context, String apiURL, SharedPreferences sharedPreferences) { + public ApacheHttpClientMediaWikiApi(Context context, + String apiURL, + SharedPreferences defaultPreferences, + SharedPreferences categoryPreferences, + Gson gson) { this.context = context; BasicHttpParams params = new BasicHttpParams(); SchemeRegistry schemeRegistry = new SchemeRegistry(); @@ -69,7 +82,9 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent()); httpClient = new DefaultHttpClient(cm, params); api = new MWApi(apiURL, httpClient); - this.sharedPreferences = sharedPreferences; + this.defaultPreferences = defaultPreferences; + this.categoryPreferences = categoryPreferences; + this.gson = gson; } @Override @@ -160,7 +175,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { } private void setAuthCookieOnLogin(boolean isLoggedIn) { - SharedPreferences.Editor editor = sharedPreferences.edit(); + SharedPreferences.Editor editor = defaultPreferences.edit(); if (isLoggedIn) { editor.putBoolean("isUserLoggedIn", true); editor.putString("getAuthCookie", api.getAuthCookie()); @@ -448,6 +463,81 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { return NotificationUtils.getNotificationsFromList(context, childNodes); } + /** + * The method takes categoryName as input and returns a List of Media objects + * It uses the generator query API to get the images in a category, 10 at a time. + * Uses the query continue values for fetching paginated responses + * @param categoryName Category name as defined on commons + * @return + */ + @Override + @NonNull + public List getCategoryImages(String categoryName) { + ApiResult apiResult = null; + try { + MWApi.RequestBuilder requestBuilder = api.action("query") + .param("generator", "categorymembers") + .param("format", "xml") + .param("gcmtype", "file") + .param("gcmtitle", categoryName) + .param("prop", "imageinfo") + .param("gcmlimit", "10") + .param("iiprop", "url|extmetadata"); + + QueryContinue queryContinueValues = getQueryContinueValues(categoryName); + if (queryContinueValues != null) { + requestBuilder.param("continue", queryContinueValues.getContinueParam()); + requestBuilder.param("gcmcontinue", queryContinueValues.getGcmContinueParam()); + } + + apiResult = requestBuilder.get(); + } catch (IOException e) { + Timber.e("Failed to obtain searchCategories", e); + } + + if (apiResult == null) { + return new ArrayList<>(); + } + + ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages"); + if (categoryImagesNode == null + || categoryImagesNode.getDocument() == null + || categoryImagesNode.getDocument().getChildNodes() == null + || categoryImagesNode.getDocument().getChildNodes().getLength() == 0) { + return new ArrayList<>(); + } + + QueryContinue queryContinue = getQueryContinue(apiResult.getNode("/api/continue").getDocument()); + setQueryContinueValues(categoryName, queryContinue); + + NodeList childNodes = categoryImagesNode.getDocument().getChildNodes(); + return CategoryImageUtils.getMediaList(childNodes); + } + + /** + * For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages + * https://www.mediawiki.org/wiki/API:Raw_query_continue + * After fetching images a page of image for a particular category, shared prefs are updated with the latest QueryContinue Values + * @param keyword + * @param queryContinue + */ + private void setQueryContinueValues(String keyword, QueryContinue queryContinue) { + SharedPreferences.Editor editor = categoryPreferences.edit(); + editor.putString(keyword, gson.toJson(queryContinue)); + editor.apply(); + } + + /** + * Before making a paginated API call, this method is called to get the latest query continue values to be used + * @param keyword + * @return + */ + @Nullable + private QueryContinue getQueryContinueValues(String keyword) { + String queryContinueString = categoryPreferences.getString(keyword, null); + return gson.fromJson(queryContinueString, QueryContinue.class); + } + @Override public boolean existingFile(String fileSha1) throws IOException { return api.action("query") 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 fd213455d..c0bd2fd87 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 @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import fr.free.nrw.commons.Media; import fr.free.nrw.commons.notification.Notification; import io.reactivex.Observable; import io.reactivex.Single; @@ -34,6 +35,8 @@ public interface MediaWikiApi { boolean logEvents(LogBuilder[] logBuilders); + List getCategoryImages(String categoryName); + @NonNull UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException; 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 acd9b7646..4a7322b57 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 @@ -23,12 +23,11 @@ import fr.free.nrw.commons.AboutActivity; import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.R; -import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.WelcomeActivity; import fr.free.nrw.commons.auth.AccountUtil; import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.contributions.ContributionsActivity; -import fr.free.nrw.commons.featured.FeaturedImagesActivity; +import fr.free.nrw.commons.category.CategoryImagesActivity; import fr.free.nrw.commons.nearby.NearbyActivity; import fr.free.nrw.commons.notification.NotificationActivity; import fr.free.nrw.commons.settings.SettingsActivity; @@ -37,6 +36,8 @@ import timber.log.Timber; public abstract class NavigationBaseActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener { + private static final String FEATURED_IMAGES_CATEGORY = "Category:Featured_pictures_on_Wikimedia_Commons"; + @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.navigation_view) @@ -157,7 +158,7 @@ public abstract class NavigationBaseActivity extends BaseActivity return true; case R.id.action_featured_images: drawerLayout.closeDrawer(navigationView); - startActivityWithFlags(this, FeaturedImagesActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + CategoryImagesActivity.startYourself(this, getString(R.string.title_activity_featured_images), FEATURED_IMAGES_CATEGORY); return true; default: Timber.e("Unknown option [%s] selected from the navigation menu", itemId); diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ContinueUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/ContinueUtils.java new file mode 100644 index 000000000..b05c8bc45 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/ContinueUtils.java @@ -0,0 +1,15 @@ +package fr.free.nrw.commons.utils; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import fr.free.nrw.commons.category.QueryContinue; + +public class ContinueUtils { + + public static QueryContinue getQueryContinue(Node document) { + Element continueElement = (Element) document; + return new QueryContinue(continueElement.getAttribute("continue"), + continueElement.getAttribute("gcmcontinue")); + } +} diff --git a/app/src/main/res/layout/activity_featured_images.xml b/app/src/main/res/layout/activity_category_images.xml similarity index 69% rename from app/src/main/res/layout/activity_featured_images.xml rename to app/src/main/res/layout/activity_category_images.xml index 755fe4983..c329e4458 100644 --- a/app/src/main/res/layout/activity_featured_images.xml +++ b/app/src/main/res/layout/activity_category_images.xml @@ -1,6 +1,5 @@ - - diff --git a/app/src/main/res/layout/fragment_featured_images.xml b/app/src/main/res/layout/fragment_category_images.xml similarity index 70% rename from app/src/main/res/layout/fragment_featured_images.xml rename to app/src/main/res/layout/fragment_category_images.xml index ca45f44c3..001f0a780 100644 --- a/app/src/main/res/layout/fragment_featured_images.xml +++ b/app/src/main/res/layout/fragment_category_images.xml @@ -1,20 +1,21 @@ - + android:background="?attr/mainBackground"> @@ -33,7 +33,7 @@ android:padding="@dimen/small_gap" > Proceed Cancel Retry + + No images found! + Error occurred while loading images. + Uploaded by: %1$s Share App diff --git a/app/src/test/kotlin/fr/free/nrw/commons/TestCommonsApplication.kt b/app/src/test/kotlin/fr/free/nrw/commons/TestCommonsApplication.kt index 73760dd40..b1de29143 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/TestCommonsApplication.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/TestCommonsApplication.kt @@ -3,6 +3,7 @@ package fr.free.nrw.commons import android.content.Context import android.content.SharedPreferences import android.support.v4.util.LruCache +import com.google.gson.Gson import com.nhaarman.mockito_kotlin.mock import com.squareup.leakcanary.RefWatcher import fr.free.nrw.commons.auth.AccountUtil @@ -36,6 +37,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu val accountUtil: AccountUtil = mock() val appSharedPreferences: SharedPreferences = mock() val defaultSharedPreferences: SharedPreferences = mock() + val categorySharedPreferences: SharedPreferences = mock() val otherSharedPreferences: SharedPreferences = mock() val uploadController: UploadController = mock() val mockSessionManager: SessionManager = mock() @@ -45,6 +47,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu val mockDbOpenHelper: DBOpenHelper = mock() val nearbyPlaces: NearbyPlaces = mock() val lruCache: LruCache = mock() + val gson: Gson = Gson() override fun providesAccountUtil(context: Context): AccountUtil = accountUtil @@ -58,7 +61,7 @@ class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModu override fun providesSessionManager(context: Context, mediaWikiApi: MediaWikiApi, sharedPreferences: SharedPreferences): SessionManager = mockSessionManager - override fun provideMediaWikiApi(context: Context, sharedPreferences: SharedPreferences): MediaWikiApi = mediaWikiApi + override fun provideMediaWikiApi(context: Context, sharedPreferences: SharedPreferences, categorySharedPreferences: SharedPreferences, gson: Gson): MediaWikiApi = mediaWikiApi override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager diff --git a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt index c51d354c2..686a90ef2 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt @@ -3,6 +3,7 @@ package fr.free.nrw.commons.mwapi import android.content.SharedPreferences import android.os.Build import android.preference.PreferenceManager +import com.google.gson.Gson import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.TestCommonsApplication import okhttp3.mockwebserver.MockResponse @@ -26,12 +27,14 @@ class ApacheHttpClientMediaWikiApiTest { private lateinit var testObject: ApacheHttpClientMediaWikiApi private lateinit var server: MockWebServer private lateinit var sharedPreferences: SharedPreferences + private lateinit var categoryPreferences: SharedPreferences @Before fun setUp() { server = MockWebServer() sharedPreferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application) - testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", sharedPreferences) + categoryPreferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application) + testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", sharedPreferences, categoryPreferences, Gson()) testObject.setWikiMediaToolforgeUrl("http://" + server.hostName + ":" + server.port + "/") } From e36ebfeffe6ced16f364101ca7356908bae015f5 Mon Sep 17 00:00:00 2001 From: Josephine Lim Date: Mon, 7 May 2018 19:27:14 +1000 Subject: [PATCH 07/12] Update pull_request_template.md (#1476) * Update pull_request_template.md * Remove Javadocs mention * Added required/optional notes --- PULL_REQUEST_TEMPLATE.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 34078f07e..9d7150008 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,15 +1,15 @@ -## Description +## Description (required) -Fixes #{GitHub issue number} +Fixes #{GitHub issue number and title} {Describe the changes made and why they were made.} -## Tests performed +## Tests performed (required) Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdDebug}. -{Please test your PR at least once before submitting.} - -## Screenshots showing what changed +## Screenshots showing what changed (optional) {Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)} + +_Note: Please ensure that you have read CONTRIBUTING.md if this is your first pull request._ From bd86dde4442cbe2f60628a1a99ebb5b01df17de4 Mon Sep 17 00:00:00 2001 From: Man Parvesh Singh Randhawa Date: Mon, 7 May 2018 17:32:11 +0800 Subject: [PATCH 08/12] resolves #1464 : MediaDataExtractor is making inefficient (redundant) server calls (#1496) --- app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java index 2d79a6c4f..affb57528 100644 --- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java +++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java @@ -61,8 +61,8 @@ public class MediaDataExtractor { } try{ - Timber.d("Nominated for deletion: " + mediaWikiApi.pageExists("Commons:Deletion_requests/"+filename)); - deletionStatus = mediaWikiApi.pageExists("Commons:Deletion_requests/"+filename); + deletionStatus = mediaWikiApi.pageExists("Commons:Deletion_requests/" + filename); + Timber.d("Nominated for deletion: " + deletionStatus); } catch (Exception e){ Timber.d(e.getMessage()); From 4f6b791c936b6559018bd5c35ea7783f6b01c688 Mon Sep 17 00:00:00 2001 From: Tanvi Dadu Date: Mon, 7 May 2018 15:27:59 +0530 Subject: [PATCH 09/12] Open map of place where picture was taken (#1360) * Intent to map added * Merge conflicts resolved * Added the functionality to hide FAB incase of null coordinate * Merge Conflict resolved * Improve pr quality * Improve Quality * Added nested FAB animations * Nested FAB implemented * Improve Quality * Added up arrow * Javadocs Added --- .../nrw/commons/upload/ShareActivity.java | 105 +++++++++++++++++- .../ic_keyboard_arrow_up_black_24dp.xml | 5 + app/src/main/res/layout/activity_share.xml | 27 ++++- app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 2 + 5 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java index 77f76fbed..6a59c8e30 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java @@ -81,8 +81,7 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.utils.ViewUtil; import timber.log.Timber; - - +import android.support.design.widget.FloatingActionButton; import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED; import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE; import static java.lang.Long.min; @@ -122,6 +121,7 @@ public class ShareActivity private Uri mediaUri; private Contribution contribution; private SimpleDraweeView backgroundImageView; + private FloatingActionButton maps_fragment; private boolean cacheFound; @@ -145,6 +145,8 @@ public class ShareActivity private long ShortAnimationDuration; private FloatingActionButton zoomInButton; private FloatingActionButton zoomOutButton; + private FloatingActionButton mainFab; + private boolean isFABOpen = false; /** @@ -284,6 +286,24 @@ public class ShareActivity if (mediaUri != null) { backgroundImageView.setImageURI(mediaUri); } + + mainFab = (FloatingActionButton) findViewById(R.id.main_fab); + /* + * called when upper arrow floating button + */ + mainFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(!isFABOpen){ + showFABMenu(); + }else{ + closeFABMenu(); + } + } + }); + + + zoomInButton = (FloatingActionButton) findViewById(R.id.media_upload_zoom_in); try { zoomInButton.setOnClickListener(new View.OnClickListener() { @@ -358,7 +378,74 @@ public class ShareActivity .commitAllowingStateLoss(); } uploadController.prepareService(); + maps_fragment = (FloatingActionButton) findViewById(R.id.media_map); + maps_fragment.setVisibility(View.VISIBLE); + if( imageObj == null || imageObj.imageCoordsExists != true){ + maps_fragment.setVisibility(View.INVISIBLE); + } + + + maps_fragment.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if( imageObj != null && imageObj.imageCoordsExists == true) { + Uri gmmIntentUri = Uri.parse("google.streetview:cbll=" + imageObj.getDecLatitude() + "," + imageObj.getDecLongitude()); + Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); + mapIntent.setPackage("com.google.android.apps.maps"); + startActivity(mapIntent); + } + } + }); } + /* + * Function to display the zoom and map FAB + */ + private void showFABMenu(){ + isFABOpen=true; + + if( imageObj != null && imageObj.imageCoordsExists == true) + maps_fragment.setVisibility(View.VISIBLE); + zoomInButton.setVisibility(View.VISIBLE); + + mainFab.animate().rotationBy(180); + maps_fragment.animate().translationY(-getResources().getDimension(R.dimen.second_fab)); + zoomInButton.animate().translationY(-getResources().getDimension(R.dimen.first_fab)); + } + + /* + * function to close the zoom and map FAB + */ + private void closeFABMenu(){ + isFABOpen=false; + mainFab.animate().rotationBy(-180); + maps_fragment.animate().translationY(0); + zoomInButton.animate().translationY(0).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + + } + + @Override + public void onAnimationEnd(Animator animator) { + if(!isFABOpen){ + maps_fragment.setVisibility(View.GONE); + zoomInButton.setVisibility(View.GONE); + } + + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }); + } + @Override public void onRequestPermissionsResult(int requestCode, @@ -461,6 +548,9 @@ public class ShareActivity detectUnwantedPicturesAsync.execute(); } + /* + * to display permission snackbar in share activity + */ private Snackbar requestPermissionUsingSnackBar(String rationale, final String[] perms, final int code) { @@ -693,7 +783,9 @@ public class ShareActivity return super.onOptionsItemSelected(item); } - // Get SHA1 of file from input stream + /* + * Get SHA1 of file from input stream + */ private String getSHA1(InputStream is) { MessageDigest digest; @@ -730,6 +822,9 @@ public class ShareActivity } } + /* + * function to provide pinch zoom + */ private void zoomImageFromThumb(final View thumbView, Uri imageuri ) { // If there's an animation in progress, cancel it // immediately and proceed with this one. @@ -737,6 +832,8 @@ public class ShareActivity CurrentAnimator.cancel(); } ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit)); + closeFABMenu(); + mainFab.setVisibility(View.GONE); InputStream input = null; Bitmap scaled = null; try { @@ -866,7 +963,7 @@ public class ShareActivity CurrentAnimator.cancel(); } zoomOutButton.setVisibility(View.GONE); - zoomInButton.setVisibility(View.VISIBLE); + mainFab.setVisibility(View.VISIBLE); // Animate the four positioning/sizing properties in parallel, // back to their original values. diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml new file mode 100644 index 000000000..bc010396b --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_share.xml b/app/src/main/res/layout/activity_share.xml index ca8097495..b6e523239 100644 --- a/app/src/main/res/layout/activity_share.xml +++ b/app/src/main/res/layout/activity_share.xml @@ -41,8 +41,6 @@ - - + + + + + + + 20sp 16sp 14sp + 15dp + 25dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e83fefd1..00bfafdad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -275,4 +275,6 @@ Error occurred while loading images. Uploaded by: %1$s Share App + Coordinates were not specified during image selection + From 2a98bd21ceff3241f2a4e4515ed56753be784415 Mon Sep 17 00:00:00 2001 From: neslihanturan Date: Mon, 7 May 2018 15:05:20 +0300 Subject: [PATCH 10/12] Add nearby tutorial (#1467) * Add dependency for MaterialShowcase * Add actionview class to get a reference to material showcase * Create a NearbyMaterialShowcaseSequence class * Apply sequence steps * Add first three steps of nearby showcase * Add sequence id constants to make sure they will be displayed only once * Add last step of sequence to explain plus fab * Create an object to prevent customize all sequences every time * Fix typo * Code cleanup * Add strings to strings.xml * Code cleanup * Revert irrelevant change * Revert irrelevant change * Remove showcaseview for recenter button * Use single showcaseView instead of sequence * Add single showcase view insted of sequence to be able to edit text style * Make sure it will be displayed only once * Cleanup * Update strings * Change dismiss text style --- app/build.gradle | 2 + .../nrw/commons/nearby/NearbyActivity.java | 91 ++++++++++++++++++- .../nrw/commons/nearby/NearbyMapFragment.java | 32 ++++++- .../NearbyMaterialShowcaseSequence.java | 18 ++++ .../fr/free/nrw/commons/utils/ViewUtil.java | 4 + app/src/main/res/values/strings.xml | 6 ++ 6 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/nearby/NearbyMaterialShowcaseSequence.java diff --git a/app/build.gradle b/app/build.gradle index 9c6f62fd4..5d37f8f54 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,6 +25,8 @@ dependencies { transitive=true } + implementation "com.github.deano2390:MaterialShowcaseView:1.2.0" + implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION" implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION" implementation "com.android.support:design:$SUPPORT_LIB_VERSION" diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java index 2a423de93..3bf8beacf 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java @@ -4,14 +4,18 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.graphics.Typeface; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.design.widget.BottomSheetBehavior; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AlertDialog; + import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -25,6 +29,7 @@ import com.google.gson.GsonBuilder; import java.util.List; import javax.inject.Inject; +import javax.inject.Named; import butterknife.BindView; import butterknife.ButterKnife; @@ -41,6 +46,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import timber.log.Timber; +import uk.co.deanwild.materialshowcaseview.IShowcaseListener; +import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView; public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener { @@ -56,12 +63,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp LinearLayout bottomSheetDetails; @BindView(R.id.transparentView) View transparentView; + @BindView(R.id.fab_recenter) + View fabRecenter; @Inject LocationServiceManager locationManager; @Inject NearbyController nearbyController; - + @Inject + @Named("application_preferences") SharedPreferences applicationPrefs; private LatLng curLatLng; private Bundle bundle; private Disposable placesDisposable; @@ -72,11 +82,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp private NearbyListFragment nearbyListFragment; private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName(); private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName(); + private View listButton; // Reference to list button to use in tutorial private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; private BroadcastReceiver broadcastReceiver; + + private boolean isListShowcaseAdded = false; + private boolean isMapShowCaseAdded = false; + private LatLng lastKnownLocation; + private MaterialShowcaseView secondSingleShowCaseView; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -126,6 +143,39 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_nearby, menu); + new Handler().post(() -> { + + listButton = findViewById(R.id.action_display_list); + + secondSingleShowCaseView = new MaterialShowcaseView.Builder(this) + .setTarget(listButton) + .setDismissText(getString(R.string.showcase_view_got_it_button)) + .setContentText(getString(R.string.showcase_view_list_icon)) + .setDelay(500) // optional but starting animations immediately in onCreate can make them choppy + .singleUse(ViewUtil.SHOWCASE_VIEW_ID_1) // provide a unique ID used to ensure it is only shown once + .setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD)) + .setListener(new IShowcaseListener() { + @Override + public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) { + + } + + // If dismissed, we can inform fragment to start showcase sequence there + @Override + public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) { + nearbyMapFragment.onNearbyMaterialShowcaseDismissed(); + } + }) + .build(); + + isListShowcaseAdded = true; + + if (isMapShowCaseAdded) { // If map showcase is also ready, start ShowcaseSequence + // Probably this case is not possible. Just added to be careful + setMapViewTutorialShowCase(); + } + }); + return super.onCreateOptionsMenu(menu); } @@ -420,6 +470,45 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp updateMapFragment(false); updateListFragment(); } + + isMapShowCaseAdded = true; + } + + public void setMapViewTutorialShowCase() { + /* + *This showcase view will be the first step of our nearbyMaterialShowcaseSequence. The reason we use a + * single item instead of adding another step to nearbyMaterialShowcaseSequence is that we are not able to + * call withoutShape() method on steps. For mapView we need an showcase view without + * any circle on it, it should cover the whole page. + * */ + MaterialShowcaseView firstSingleShowCaseView = new MaterialShowcaseView.Builder(this) + .setTarget(nearbyMapFragment.mapView) + .setDismissText(getString(R.string.showcase_view_got_it_button)) + .setContentText(getString(R.string.showcase_view_whole_nearby_activity)) + .setDelay(500) // optional but starting animations immediately in onCreate can make them choppy + .singleUse(ViewUtil.SHOWCASE_VIEW_ID_2) // provide a unique ID used to ensure it is only shown once + .withoutShape() // no shape on map view since there are no view to focus on + .setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD)) + .setListener(new IShowcaseListener() { + @Override + public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) { + + } + + @Override + public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) { + /* Add other nearbyMaterialShowcaseSequence here, it will make the user feel as they are a + * nearbyMaterialShowcaseSequence whole together. + * */ + secondSingleShowCaseView.show(NearbyActivity.this); + } + }) + .build(); + + if (applicationPrefs.getBoolean("firstRunNearby", true)) { + applicationPrefs.edit().putBoolean("firstRunNearby", false).apply(); + firstSingleShowCaseView.show(this); + } } private void lockNearbyView(boolean lock) { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java index 431132436..69041d286 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Color; +import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; @@ -58,13 +59,14 @@ import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.utils.UriDeserializer; import fr.free.nrw.commons.utils.ViewUtil; import timber.log.Timber; +import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView; import static android.app.Activity.RESULT_OK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; public class NearbyMapFragment extends DaggerFragment { - private MapView mapView; + public MapView mapView; private List baseMarkerOptions; private fr.free.nrw.commons.location.LatLng curLatLng; public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates; @@ -111,6 +113,10 @@ public class NearbyMapFragment extends DaggerFragment { private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06; private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.04; + private boolean isSecondMaterialShowcaseDismissed; + private boolean isMapReady; + private MaterialShowcaseView thirdSingleShowCaseView; + private Bundle bundleForUpdtes;// Carry information from activity about changed nearby places and current location @Inject @@ -163,7 +169,6 @@ public class NearbyMapFragment extends DaggerFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Timber.d("onCreateView called"); if (curLatLng != null) { Timber.d("curLatLng found, setting up map view..."); @@ -476,6 +481,7 @@ public class NearbyMapFragment extends DaggerFragment { mapView.getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(MapboxMap mapboxMap) { + ((NearbyActivity)getActivity()).setMapViewTutorialShowCase(); NearbyMapFragment.this.mapboxMap = mapboxMap; updateMapSignificantly(); } @@ -519,6 +525,7 @@ public class NearbyMapFragment extends DaggerFragment { private void addNearbyMarkerstoMapBoxMap() { mapboxMap.addMarkers(baseMarkerOptions); + mapboxMap.setOnInfoWindowCloseListener(marker -> { if (marker == selected) { bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); @@ -534,6 +541,7 @@ public class NearbyMapFragment extends DaggerFragment { }); mapboxMap.setOnMarkerClickListener(marker -> { + if (marker instanceof NearbyMarker) { this.selected = marker; NearbyMarker nearbyMarker = (NearbyMarker) marker; @@ -541,6 +549,7 @@ public class NearbyMapFragment extends DaggerFragment { passInfoToSheet(place); bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + } return false; }); @@ -634,7 +643,19 @@ public class NearbyMapFragment extends DaggerFragment { addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId()); addAnchorToSmallFABs(fabCamera, getActivity().findViewById(R.id.empty_view1).getId()); + thirdSingleShowCaseView = new MaterialShowcaseView.Builder(this.getActivity()) + .setTarget(fabPlus) + .setDismissText(getString(R.string.showcase_view_got_it_button)) + .setContentText(getString(R.string.showcase_view_plus_fab)) + .setDelay(500) // optional but starting animations immediately in onCreate can make them choppy + .singleUse(ViewUtil.SHOWCASE_VIEW_ID_3) // provide a unique ID used to ensure it is only shown once + .setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD)) + .build(); + isMapReady = true; + if (isSecondMaterialShowcaseDismissed) { + thirdSingleShowCaseView.show(getActivity()); + } } @@ -791,6 +812,13 @@ public class NearbyMapFragment extends DaggerFragment { this.bundleForUpdtes = bundleForUpdtes; } + public void onNearbyMaterialShowcaseDismissed() { + isSecondMaterialShowcaseDismissed = true; + if (isMapReady) { + thirdSingleShowCaseView.show(getActivity()); + } + } + @Override public void onStart() { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMaterialShowcaseSequence.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMaterialShowcaseSequence.java new file mode 100644 index 000000000..c6e46611d --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMaterialShowcaseSequence.java @@ -0,0 +1,18 @@ +package fr.free.nrw.commons.nearby; + +import android.app.Activity; + +import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence; +import uk.co.deanwild.materialshowcaseview.ShowcaseConfig; + + +public class NearbyMaterialShowcaseSequence extends MaterialShowcaseSequence { + + public NearbyMaterialShowcaseSequence(Activity activity, String sequenceID) { + super(activity, sequenceID); + ShowcaseConfig config = new ShowcaseConfig(); + config.setDelay(500); // half second between each showcase view + this.setConfig(config); + this.singleUse(sequenceID); // Display tutorial only once + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java index 1c45e8178..0c22a40a2 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java @@ -10,6 +10,10 @@ import android.widget.Toast; public class ViewUtil { + public static final String SHOWCASE_VIEW_ID_1 = "SHOWCASE_VIEW_ID_1"; + public static final String SHOWCASE_VIEW_ID_2 = "SHOWCASE_VIEW_ID_2"; + public static final String SHOWCASE_VIEW_ID_3 = "SHOWCASE_VIEW_ID_3"; + public static void showSnackbar(View view, int messageResourceId) { Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show(); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 00bfafdad..dcea51bcc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -271,9 +271,15 @@ Cancel Retry + Got it! + These are the places near you that need pictures to illustrate their Wikipedia articles + Tapping this button brings up a list of these places + You can upload a picture for any place from your gallery or camera + No images found! Error occurred while loading images. Uploaded by: %1$s + Share App Coordinates were not specified during image selection From cbca264dbcae19a67ea27f7cdac226116f750b06 Mon Sep 17 00:00:00 2001 From: Kaartic Sivaraam Date: Tue, 8 May 2018 06:34:09 +0000 Subject: [PATCH 11/12] CONTRIBUTING: fix formatting of the gist of the guidelines (#1453) * CONTRIBUTING: fix formatting of the gist of the guidelines First level headings for a gist seems to be overkill. So, replace first level headings with an ordered-list which sounds more meaningful. * CONTRIBUTING: specify clearly that 'blame' is a feature of "Git" The contributing file specifies about the ability to know who wrote something without the need of @author javadoc tags but incorrectly attributes the feature to GitHub. Correctly attribute the feature to where it belongs, Git, and specify the name of the feature to help users easily take advantage of it. --- CONTRIBUTING.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a8e7af19..caa02a103 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,23 +5,30 @@ If you're not sure where to start head on to [this wiki page](https://github.com Here's a gist of the guidelines, -# Make separate commits for logically separate changes +1. Make separate commits for logically separate changes -# Describe your changes well in the commit message +1. Describe your changes well in the commit message -The first line of the commit message should be a short description of what has + The first line of the commit message should be a short description of what has changed. It is also good to prefix the first line with "area: " where the "area" is a filename or identifier for the general area of the code being modified. The body should provide a meaningful commit message. -# Write Javadocs +1. Write Javadocs -We require contributors to include Javadocs for all new methods and classes submitted via PRs (after 1 May 2018). This is aimed at making it easier for new contributors to dive into our codebase, especially those who are new to Android development. A few things to note: + We require contributors to include Javadocs for all new methods and classes + submitted via PRs (after 1 May 2018). This is aimed at making it easier for + new contributors to dive into our codebase, especially those who are new to + Android development. A few things to note: -- This should not replace the need for code that is easily-readable in and of itself -- Please make sure that your Javadocs are reasonably descriptive, not just a copy of the method name -- Please do not use `@author` tags - we aim for collective code ownership, and if needed, GitHub allows us to see who wrote something without needing to add these tags + - This should not replace the need for code that is easily-readable in + and of itself + - Please make sure that your Javadocs are reasonably descriptive, not just + a copy of the method name + - Please do not use `@author` tags - we aim for collective code ownership, + and if needed, Git allows us to see who wrote something without needing + to add these tags (`git blame`) -# Write tests for your code (if possible) +1. Write tests for your code (if possible) -# Make sure the Wiki pages don't become stale by updating them (if needed) +1. Make sure the Wiki pages don't become stale by updating them (if needed) From d9600297752f8d7fa6955bd44678811d64ddc8eb Mon Sep 17 00:00:00 2001 From: Ashish Kumar Date: Tue, 8 May 2018 12:21:13 +0530 Subject: [PATCH 12/12] Feature/switch to butterknife (#1494) * Implemented butterknife in MediaDetailFragment [issue #1491] * Implemented butterknife in MediaDetailPagerFragment [[issue #1491]] * post merge upstream master wip [[issue #1491]] --- .../commons/media/MediaDetailFragment.java | 200 ++++++++++-------- .../media/MediaDetailPagerFragment.java | 7 +- 2 files changed, 112 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index c3a977ab5..9614c4f00 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -9,6 +9,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.Nullable; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.TypedValue; import android.view.LayoutInflater; @@ -22,6 +23,9 @@ import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -45,7 +49,8 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.ui.widget.CompatTextView; import timber.log.Timber; -import static android.view.View.*; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; import static android.widget.Toast.LENGTH_SHORT; public class MediaDetailFragment extends CommonsDaggerSupportFragment { @@ -75,23 +80,37 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { @Inject MediaWikiApi mwApi; - - private MediaWikiImageView image; - private MediaDetailSpacer spacer; private int initialListTop = 0; - private TextView title; - private TextView desc; - private TextView author; - private TextView license; - private TextView coordinates; - private TextView uploadedDate; - private TextView seeMore; - private LinearLayout nominatedforDeletion; - private LinearLayout categoryContainer; - private LinearLayout authorLayout; - private Button delete; - private ScrollView scrollView; + @BindView(R.id.mediaDetailImage) + MediaWikiImageView image; + @BindView(R.id.mediaDetailSpacer) + MediaDetailSpacer spacer; + @BindView(R.id.mediaDetailTitle) + TextView title; + @BindView(R.id.mediaDetailDesc) + TextView desc; + @BindView(R.id.mediaDetailAuthor) + TextView author; + @BindView(R.id.mediaDetailLicense) + TextView license; + @BindView(R.id.mediaDetailCoordinates) + TextView coordinates; + @BindView(R.id.mediaDetailuploadeddate) + TextView uploadedDate; + @BindView(R.id.seeMore) + TextView seeMore; + @BindView(R.id.nominatedDeletionBanner) + LinearLayout nominatedForDeletion; + @BindView(R.id.mediaDetailCategoryContainer) + LinearLayout categoryContainer; + @BindView(R.id.authorLinearLayout) + LinearLayout authorLayout; + @BindView(R.id.nominateDeletion) + Button delete; + @BindView(R.id.mediaDetailScrollView) + ScrollView scrollView; + private ArrayList categoryNames; private boolean categoriesLoaded = false; private boolean categoriesPresent = false; @@ -101,6 +120,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { private AsyncTask detailFetchTask; private LicenseList licenseList; + //Had to make this class variable, to implement various onClicks, which access the media, also I fell why make separate variables when one can serve the purpose + private Media media; + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -137,22 +159,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { final View view = inflater.inflate(R.layout.fragment_media_detail, container, false); - image = (MediaWikiImageView) view.findViewById(R.id.mediaDetailImage); - scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView); - - // Detail consists of a list view with main pane in header view, plus category list. - spacer = (MediaDetailSpacer) view.findViewById(R.id.mediaDetailSpacer); - title = (TextView) view.findViewById(R.id.mediaDetailTitle); - desc = (TextView) view.findViewById(R.id.mediaDetailDesc); - author = (TextView) view.findViewById(R.id.mediaDetailAuthor); - license = (TextView) view.findViewById(R.id.mediaDetailLicense); - coordinates = (TextView) view.findViewById(R.id.mediaDetailCoordinates); - uploadedDate = (TextView) view.findViewById(R.id.mediaDetailuploadeddate); - seeMore = (TextView) view.findViewById(R.id.seeMore); - nominatedforDeletion = (LinearLayout) view.findViewById(R.id.nominatedDeletionBanner); - delete = (Button) view.findViewById(R.id.nominateDeletion); - categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer); - authorLayout = (LinearLayout) view.findViewById(R.id.authorLinearLayout); + ButterKnife.bind(this,view); if (isFeaturedMedia){ authorLayout.setVisibility(VISIBLE); @@ -196,7 +203,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { @Override public void onResume() { super.onResume(); - Media media = detailProvider.getMediaAtPosition(index); + media = detailProvider.getMediaAtPosition(index); if (media == null) { // Ask the detail provider to ping us when we're ready Timber.d("MediaDetailFragment not yet ready to display details; registering observer"); @@ -209,17 +216,18 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { Timber.d("MediaDetailFragment ready to display delayed details!"); detailProvider.unregisterDataSetObserver(dataObserver); dataObserver = null; - displayMediaDetails(detailProvider.getMediaAtPosition(index)); + media=detailProvider.getMediaAtPosition(index); + displayMediaDetails(); } }; detailProvider.registerDataSetObserver(dataObserver); } else { Timber.d("MediaDetailFragment ready to display details"); - displayMediaDetails(media); + displayMediaDetails(); } } - private void displayMediaDetails(final Media media) { + private void displayMediaDetails() { //Always load image from Internet to allow viewing the desc, license, and cats image.setMedia(media); @@ -256,7 +264,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { if (success) { extractor.fill(media); setTextFields(media); - setOnClickListeners(media); } else { Timber.d("Failed to load photo details."); } @@ -316,74 +323,81 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { checkDeletion(media); } - private void setOnClickListeners(final Media media) { - if (licenseLink(media) != null) { - license.setOnClickListener(v -> openWebBrowser(licenseLink(media))); + @OnClick(R.id.mediaDetailLicense) + public void onMediaDetailLicenceClicked(){ + if (!TextUtils.isEmpty(licenseLink(media))) { + openWebBrowser(licenseLink(media)); } else { if(isFeaturedMedia) { - Timber.d("Unable to fetch license URL for %s", media.getLicense()); + Timber.d("Unable to fetch license URL for %s", media.getLicense()); } else { Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT); toast.show(); } } + } + + @OnClick(R.id.mediaDetailCoordinates) + public void onMediaDetailCoordinatesClicked(){ if (media.getCoordinates() != null) { - coordinates.setOnClickListener(v -> openMap(media.getCoordinates())); + openMap(media.getCoordinates()); } - if (delete.getVisibility() == VISIBLE) { - enableDeleteButton(true); + } - delete.setOnClickListener(v -> { + @OnClick(R.id.nominateDeletion) + public void onDeleteButtonClicked(){ + //Reviewer correct me if i have misunderstood something over here + //But how does this if (delete.getVisibility() == View.VISIBLE) { + // enableDeleteButton(true); makes sense ? + AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); + alert.setMessage("Why should this file be deleted?"); + final EditText input = new EditText(getActivity()); + alert.setView(input); + input.requestFocus(); + alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + String reason = input.getText().toString(); + DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason); + deleteTask.execute(); + enableDeleteButton(false); + } + }); + alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + } + }); + AlertDialog d = alert.create(); + input.addTextChangedListener(new TextWatcher() { + private void handleText() { + final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE); + if (input.getText().length() == 0) { + okButton.setEnabled(false); + } else { + okButton.setEnabled(true); + } + } - AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); - alert.setMessage("Why should this file be deleted?"); - final EditText input = new EditText(getActivity()); - alert.setView(input); - input.requestFocus(); - alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - String reason = input.getText().toString(); - DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason); - deleteTask.execute(); - enableDeleteButton(false); - } - }); - alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - } - }); - AlertDialog d = alert.create(); - input.addTextChangedListener(new TextWatcher() { - private void handleText() { - final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE); - if (input.getText().length() == 0) { - okButton.setEnabled(false); - } else { - okButton.setEnabled(true); - } - } + @Override + public void afterTextChanged(Editable arg0) { + handleText(); + } - @Override - public void afterTextChanged(Editable arg0) { - handleText(); - } + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + d.show(); + d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - d.show(); - d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - }); - } - if (nominatedforDeletion.getVisibility() == VISIBLE){ - seeMore.setOnClickListener(v -> { - openWebBrowser(media.getFilePageTitle().getMobileUri().toString()); - }); + @OnClick(R.id.seeMore) + public void onSeeMoreClicked(){ + if(nominatedForDeletion.getVisibility()== VISIBLE) { + openWebBrowser(media.getFilePageTitle().getMobileUri().toString()); } } @@ -488,11 +502,11 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { private void checkDeletion(Media media){ if (media.getRequestedDeletion()){ delete.setVisibility(GONE); - nominatedforDeletion.setVisibility(VISIBLE); + nominatedForDeletion.setVisibility(VISIBLE); } else{ delete.setVisibility(VISIBLE); - nominatedforDeletion.setVisibility(GONE); + nominatedForDeletion.setVisibility(GONE); } } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index c0564c603..62d1261cf 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -26,6 +26,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; +import butterknife.BindView; +import butterknife.ButterKnife; import javax.inject.Inject; import javax.inject.Named; @@ -53,7 +55,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple @Named("default_preferences") SharedPreferences prefs; - private ViewPager pager; + @BindView(R.id.mediaDetailsPager) + ViewPager pager; private Boolean editable; private boolean isFeaturedImage; @@ -72,7 +75,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_media_detail_pager, container, false); - pager = (ViewPager) view.findViewById(R.id.mediaDetailsPager); + ButterKnife.bind(this,view); pager.addOnPageChangeListener(this); final MediaDetailAdapter adapter = new MediaDetailAdapter(getChildFragmentManager());