From 4194409cd2add399f33b29c73bae078f3a42fda4 Mon Sep 17 00:00:00 2001
From: Ayan Sarkar <71203077+Ayan-10@users.noreply.github.com>
Date: Wed, 23 Mar 2022 13:03:54 +0530
Subject: [PATCH] Fixes 4544 : Language selection: history (#4880)
* Xml changes
* Content provider created
* Database setup done
* Database setup revised
* Database setup revised
* SettingsFragment finished
* SettingsFragment finished
* UploadMediaDetailFragment updated
* UploadMediaDetailFragment updated
* Java docs
* Test fixed
* Test added
* Test added
* Test updated
* More tests added
---
app/build.gradle | 2 +
app/src/main/AndroidManifest.xml | 7 +
.../free/nrw/commons/data/DBOpenHelper.java | 3 +
.../commons/di/CommonsApplicationModule.java | 13 +
.../di/ContentProviderBuilderModule.java | 4 +
.../nrw/commons/recentlanguages/Language.kt | 3 +
.../recentlanguages/RecentLanguagesAdapter.kt | 68 ++++++
.../RecentLanguagesContentProvider.java | 122 ++++++++++
.../recentlanguages/RecentLanguagesDao.java | 208 ++++++++++++++++
.../commons/settings/SettingsFragment.java | 100 +++++++-
.../nrw/commons/upload/LanguagesAdapter.kt | 7 +
.../upload/UploadMediaDetailAdapter.java | 104 +++++++-
.../UploadMediaDetailFragment.java | 6 +-
.../res/layout/dialog_select_language.xml | 45 +++-
app/src/main/res/values/strings.xml | 1 +
.../RecentLanguagesAdapterUnitTest.kt | 114 +++++++++
.../RecentLanguagesContentProviderUnitTest.kt | 37 +++
.../RecentLanguagesDaoUnitTest.kt | 227 ++++++++++++++++++
.../settings/SettingsFragmentUnitTests.kt | 146 +++++++++++
.../UploadMediaDetailAdapterUnitTest.kt | 104 +++++++-
20 files changed, 1312 insertions(+), 9 deletions(-)
create mode 100644 app/src/main/java/fr/free/nrw/commons/recentlanguages/Language.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapter.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.java
create mode 100644 app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapterUnitTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProviderUnitTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt
diff --git a/app/build.gradle b/app/build.gradle
index dd31600b7..864ab2743 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -259,6 +259,7 @@ android {
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.categories.contentprovider\""
buildConfigField "String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.explore.recentsearches.contentprovider\""
+ buildConfigField "String", "RECENT_LANGUAGE_AUTHORITY", "\"fr.free.nrw.commons.recentlanguages.contentprovider\""
buildConfigField "String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.contentprovider\""
buildConfigField "String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.locations.contentprovider\""
buildConfigField "String", "BOOKMARK_ITEMS_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.items.contentprovider\""
@@ -294,6 +295,7 @@ android {
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.beta.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.beta.categories.contentprovider\""
buildConfigField "String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.beta.explore.recentsearches.contentprovider\""
+ buildConfigField "String", "RECENT_LANGUAGE_AUTHORITY", "\"fr.free.nrw.commons.beta.recentlanguages.contentprovider\""
buildConfigField "String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.contentprovider\""
buildConfigField "String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.locations.contentprovider\""
buildConfigField "String", "BOOKMARK_ITEMS_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.items.contentprovider\""
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8babf5cf7..8fc66230f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -194,6 +194,13 @@
android:label="@string/provider_searches"
android:syncable="false" />
+
+
,
+ private val selectedLanguages: HashMap<*, String>
+) : ArrayAdapter(context, R.layout.row_item_languages_spinner) {
+
+ /**
+ * Selected language code in UploadMediaDetailAdapter
+ * Used for marking selected ones
+ */
+ var selectedLangCode = ""
+
+ override fun isEnabled(position: Int) = recentLanguages[position].languageCode.let {
+ it.isNotEmpty() && !selectedLanguages.containsValue(it) && it != selectedLangCode
+ }
+
+ override fun getCount() = recentLanguages.size
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val rowView: View = convertView
+ ?: LayoutInflater.from(context)
+ .inflate(R.layout.row_item_languages_spinner, parent, false)
+ val languageCode = recentLanguages[position].languageCode
+ val languageName = recentLanguages[position].languageName
+ rowView.tv_language.let {
+ it.isEnabled = isEnabled(position)
+ if (languageCode.isEmpty()) {
+ it.text = StringUtils.capitalize(languageName)
+ it.textAlignment = View.TEXT_ALIGNMENT_CENTER
+ } else {
+ it.text =
+ "${StringUtils.capitalize(languageName)}" +
+ " [${LangCodeUtils.fixLanguageCode(languageCode)}]"
+ }
+ }
+ return rowView
+ }
+
+ /**
+ * Provides code of a language from recent languages for a specific position
+ */
+ fun getLanguageCode(position: Int): String {
+ return recentLanguages[position].languageCode
+ }
+
+ /**
+ * Provides name of a language from recent languages for a specific position
+ */
+ fun getLanguageName(position: Int): String {
+ return recentLanguages[position].languageName
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.java b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.java
new file mode 100644
index 000000000..de94c4b09
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProvider.java
@@ -0,0 +1,122 @@
+package fr.free.nrw.commons.recentlanguages;
+
+import static fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.COLUMN_NAME;
+import static fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.TABLE_NAME;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import fr.free.nrw.commons.BuildConfig;
+import fr.free.nrw.commons.data.DBOpenHelper;
+import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
+import javax.inject.Inject;
+import timber.log.Timber;
+
+/**
+ * Content provider of recently used languages
+ */
+public class RecentLanguagesContentProvider extends CommonsDaggerContentProvider {
+
+ private static final String BASE_PATH = "recent_languages";
+ public static final Uri BASE_URI =
+ Uri.parse("content://" + BuildConfig.RECENT_LANGUAGE_AUTHORITY + "/" + BASE_PATH);
+
+
+ /**
+ * Append language code to the base uri
+ * @param languageCode Code of a language
+ */
+ public static Uri uriForCode(final String languageCode) {
+ return Uri.parse(BASE_URI + "/" + languageCode);
+ }
+
+ @Inject
+ DBOpenHelper dbOpenHelper;
+
+ @Override
+ public String getType(@NonNull final Uri uri) {
+ return null;
+ }
+
+ /**
+ * Queries the SQLite database for the recently used languages
+ * @param uri : contains the uri for recently used languages
+ * @param projection : contains the all fields of the table
+ * @param selection : handles Where
+ * @param selectionArgs : the condition of Where clause
+ * @param sortOrder : ascending or descending
+ */
+ @Override
+ public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setTables(TABLE_NAME);
+ final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
+ final Cursor cursor = queryBuilder.query(db, projection, selection,
+ selectionArgs, null, null, sortOrder);
+ cursor.setNotificationUri(getContext().getContentResolver(), uri);
+ return cursor;
+ }
+
+ /**
+ * Handles the update query of local SQLite Database
+ * @param uri : contains the uri for recently used languages
+ * @param contentValues : new values to be entered to db
+ * @param selection : handles Where
+ * @param selectionArgs : the condition of Where clause
+ */
+ @Override
+ public int update(@NonNull final Uri uri, final ContentValues contentValues,
+ final String selection, final String[] selectionArgs) {
+ final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
+ final int rowsUpdated;
+ if (TextUtils.isEmpty(selection)) {
+ final int id = Integer.parseInt(uri.getLastPathSegment());
+ rowsUpdated = sqlDB.update(TABLE_NAME,
+ contentValues,
+ COLUMN_NAME + " = ?",
+ new String[]{String.valueOf(id)});
+ } else {
+ throw new IllegalArgumentException(
+ "Parameter `selection` should be empty when updating an ID");
+ }
+
+ getContext().getContentResolver().notifyChange(uri, null);
+ return rowsUpdated;
+ }
+
+ /**
+ * Handles the insertion of new recently used languages record to local SQLite Database
+ * @param uri : contains the uri for recently used languages
+ * @param contentValues : new values to be entered to db
+ */
+ @Override
+ public Uri insert(@NonNull final Uri uri, final ContentValues contentValues) {
+ final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
+ final long id = sqlDB.insert(TABLE_NAME, null, contentValues);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return Uri.parse(BASE_URI + "/" + id);
+ }
+
+ /**
+ * Handles the deletion of new recently used languages record to local SQLite Database
+ * @param uri : contains the uri for recently used languages
+ */
+ @Override
+ public int delete(@NonNull final Uri uri, final String s, final String[] strings) {
+ final int rows;
+ final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
+ Timber.d("Deleting recently used language %s", uri.getLastPathSegment());
+ rows = db.delete(
+ TABLE_NAME,
+ "language_code = ?",
+ new String[]{uri.getLastPathSegment()}
+ );
+ getContext().getContentResolver().notifyChange(uri, null);
+ return rows;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java
new file mode 100644
index 000000000..1af1b50e6
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java
@@ -0,0 +1,208 @@
+package fr.free.nrw.commons.recentlanguages;
+
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.RemoteException;
+import androidx.annotation.NonNull;
+import java.util.ArrayList;
+import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+/**
+ * Handles database operations for recently used languages
+ */
+@Singleton
+public class RecentLanguagesDao {
+
+ private final Provider clientProvider;
+
+ @Inject
+ public RecentLanguagesDao
+ (@Named("recent_languages") final Provider clientProvider) {
+ this.clientProvider = clientProvider;
+ }
+
+ /**
+ * Find all persisted recently used languages on database
+ * @return list of recently used languages
+ */
+ public List getRecentLanguages() {
+ final List languages = new ArrayList<>();
+ final ContentProviderClient db = clientProvider.get();
+ try (final Cursor cursor = db.query(
+ RecentLanguagesContentProvider.BASE_URI,
+ RecentLanguagesDao.Table.ALL_FIELDS,
+ null,
+ new String[]{},
+ null)) {
+ if(cursor != null && cursor.moveToLast()) {
+ do {
+ languages.add(fromCursor(cursor));
+ } while (cursor.moveToPrevious());
+ }
+ } catch (final RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ return languages;
+ }
+
+ /**
+ * Add a Language to database
+ * @param language : Language to add
+ */
+ public void addRecentLanguage(final Language language) {
+ final ContentProviderClient db = clientProvider.get();
+ try {
+ db.insert(RecentLanguagesContentProvider.BASE_URI, toContentValues(language));
+ } catch (final RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ }
+
+ /**
+ * Delete a language from database
+ * @param languageCode : code of the Language to delete
+ */
+ public void deleteRecentLanguage(final String languageCode) {
+ final ContentProviderClient db = clientProvider.get();
+ try {
+ db.delete(RecentLanguagesContentProvider.uriForCode(languageCode), null, null);
+ } catch (final RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ }
+
+ /**
+ * Find a language from database based on its name
+ * @param languageCode : code of the Language to find
+ * @return boolean : is language in database ?
+ */
+ public boolean findRecentLanguage(final String languageCode) {
+ if (languageCode == null) { //Avoiding NPE's
+ return false;
+ }
+ final ContentProviderClient db = clientProvider.get();
+ try (final Cursor cursor = db.query(
+ RecentLanguagesContentProvider.BASE_URI,
+ RecentLanguagesDao.Table.ALL_FIELDS,
+ Table.COLUMN_CODE + "=?",
+ new String[]{languageCode},
+ null
+ )) {
+ if (cursor != null && cursor.moveToFirst()) {
+ return true;
+ }
+ } catch (final RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ return false;
+ }
+
+ /**
+ * It creates an Recent Language object from data stored in the SQLite DB by using cursor
+ * @param cursor cursor
+ * @return Language object
+ */
+ @NonNull
+ Language fromCursor(final Cursor cursor) {
+ // Hardcoding column positions!
+ final String languageName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME));
+ final String languageCode = cursor.getString(cursor.getColumnIndex(Table.COLUMN_CODE));
+ return new Language(languageName, languageCode);
+ }
+
+ /**
+ * Takes data from Language and create a content value object
+ * @param recentLanguage recently used language
+ * @return ContentValues
+ */
+ private ContentValues toContentValues(final Language recentLanguage) {
+ final ContentValues cv = new ContentValues();
+ cv.put(Table.COLUMN_NAME, recentLanguage.getLanguageName());
+ cv.put(Table.COLUMN_CODE, recentLanguage.getLanguageCode());
+ return cv;
+ }
+
+ /**
+ * This class contains the database table architecture for recently used languages,
+ * It also contains queries and logic necessary to the create, update, delete this table.
+ */
+ public static final class Table {
+ public static final String TABLE_NAME = "recent_languages";
+ static final String COLUMN_NAME = "language_name";
+ static final String COLUMN_CODE = "language_code";
+
+ // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
+ public static final String[] ALL_FIELDS = {
+ COLUMN_NAME,
+ COLUMN_CODE
+ };
+
+ static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
+
+ static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ + COLUMN_NAME + " STRING,"
+ + COLUMN_CODE + " STRING PRIMARY KEY"
+ + ");";
+
+ /**
+ * This method creates a LanguagesTable in SQLiteDatabase
+ * @param db SQLiteDatabase
+ */
+ public static void onCreate(final SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_STATEMENT);
+ }
+
+ /**
+ * This method deletes LanguagesTable from SQLiteDatabase
+ * @param db SQLiteDatabase
+ */
+ public static void onDelete(final SQLiteDatabase db) {
+ db.execSQL(DROP_TABLE_STATEMENT);
+ onCreate(db);
+ }
+
+ /**
+ * This method is called on migrating from a older version to a newer version
+ * @param db SQLiteDatabase
+ * @param from Version from which we are migrating
+ * @param to Version to which we are migrating
+ */
+ public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
+ if (from == to) {
+ return;
+ }
+ if (from < 6) {
+ // doesn't exist yet
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ if (from == 6) {
+ // table added in version 7
+ onCreate(db);
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ if (from == 7) {
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
index 2d3856506..dcafa71d0 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
@@ -9,7 +9,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@@ -18,6 +17,7 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.ListView;
+import android.widget.TextView;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
@@ -39,10 +39,12 @@ import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.logging.CommonsLogSender;
+import fr.free.nrw.commons.recentlanguages.Language;
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter;
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
import fr.free.nrw.commons.upload.LanguagesAdapter;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -60,10 +62,16 @@ public class SettingsFragment extends PreferenceFragmentCompat {
@Inject
CommonsLogSender commonsLogSender;
+ @Inject
+ RecentLanguagesDao recentLanguagesDao;
+
private ListPreference themeListPreference;
private Preference descriptionLanguageListPreference;
private Preference appUiLanguageListPreference;
private String keyLanguageListPreference;
+ private TextView recentLanguagesTextView;
+ private View separator;
+ private ListView languageHistoryListView;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -211,6 +219,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
// Gets current language code from shared preferences
final String languageCode = getCurrentLanguageCode(keyListPreference);
+ final List recentLanguages = recentLanguagesDao.getRecentLanguages();
HashMap selectedLanguages = new HashMap<>();
if (keyListPreference.equals("appUiDefaultLanguagePref")) {
@@ -246,6 +255,11 @@ public class SettingsFragment extends PreferenceFragmentCompat {
EditText editText = dialog.findViewById(R.id.search_language);
ListView listView = dialog.findViewById(R.id.language_list);
+ languageHistoryListView = dialog.findViewById(R.id.language_history_list);
+ recentLanguagesTextView = dialog.findViewById(R.id.recent_searches);
+ separator = dialog.findViewById(R.id.separator);
+
+ setUpRecentLanguagesSection(recentLanguages, selectedLanguages);
listView.setAdapter(languagesAdapter);
@@ -253,7 +267,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
int i2) {
-
+ hideRecentLanguagesSection();
}
@Override
@@ -268,12 +282,23 @@ public class SettingsFragment extends PreferenceFragmentCompat {
}
});
+ languageHistoryListView.setOnItemClickListener((adapterView, view, position, id) -> {
+ onRecentLanguageClicked(keyListPreference, dialog, adapterView, position);
+ });
+
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView> adapterView, View view, int i,
long l) {
String languageCode = ((LanguagesAdapter) adapterView.getAdapter())
.getLanguageCode(i);
+ final String languageName = ((LanguagesAdapter) adapterView.getAdapter())
+ .getLanguageName(i);
+ final boolean isExists = recentLanguagesDao.findRecentLanguage(languageCode);
+ if (isExists) {
+ recentLanguagesDao.deleteRecentLanguage(languageCode);
+ }
+ recentLanguagesDao.addRecentLanguage(new Language(languageName, languageCode));
saveLanguageValue(languageCode, keyListPreference);
Locale defLocale = new Locale(languageCode);
if(keyListPreference.equals("appUiDefaultLanguagePref")) {
@@ -293,6 +318,75 @@ public class SettingsFragment extends PreferenceFragmentCompat {
dialogInterface -> languagesAdapter.getFilter().filter(""));
}
+ /**
+ * Set up recent languages section
+ *
+ * @param recentLanguages recently used languages
+ * @param selectedLanguages selected languages
+ */
+ private void setUpRecentLanguagesSection(List recentLanguages,
+ HashMap selectedLanguages) {
+ if (recentLanguages.isEmpty()) {
+ languageHistoryListView.setVisibility(View.GONE);
+ recentLanguagesTextView.setVisibility(View.GONE);
+ separator.setVisibility(View.GONE);
+ } else {
+ if (recentLanguages.size() > 5) {
+ for (int i = recentLanguages.size()-1; i >=5; i--) {
+ recentLanguagesDao
+ .deleteRecentLanguage(recentLanguages.get(i).getLanguageCode());
+ }
+ }
+ languageHistoryListView.setVisibility(View.VISIBLE);
+ recentLanguagesTextView.setVisibility(View.VISIBLE);
+ separator.setVisibility(View.VISIBLE);
+ final RecentLanguagesAdapter recentLanguagesAdapter
+ = new RecentLanguagesAdapter(
+ getActivity(),
+ recentLanguagesDao.getRecentLanguages(),
+ selectedLanguages);
+ languageHistoryListView.setAdapter(recentLanguagesAdapter);
+ }
+ }
+
+ /**
+ * Handles click event for recent language section
+ */
+ private void onRecentLanguageClicked(String keyListPreference, Dialog dialog, AdapterView> adapterView,
+ int position) {
+ final String recentLanguageCode = ((RecentLanguagesAdapter) adapterView.getAdapter())
+ .getLanguageCode(position);
+ final String recentLanguageName = ((RecentLanguagesAdapter) adapterView.getAdapter())
+ .getLanguageName(position);
+ final boolean isExists = recentLanguagesDao.findRecentLanguage(recentLanguageCode);
+ if (isExists) {
+ recentLanguagesDao.deleteRecentLanguage(recentLanguageCode);
+ }
+ recentLanguagesDao.addRecentLanguage(
+ new Language(recentLanguageName, recentLanguageCode));
+ saveLanguageValue(recentLanguageCode, keyListPreference);
+ final Locale defLocale = new Locale(recentLanguageCode);
+ if (keyListPreference.equals("appUiDefaultLanguagePref")) {
+ appUiLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale));
+ setLocale(Objects.requireNonNull(getActivity()), recentLanguageCode);
+ getActivity().recreate();
+ final Intent intent = new Intent(getActivity(), MainActivity.class);
+ startActivity(intent);
+ } else {
+ descriptionLanguageListPreference.setSummary(defLocale.getDisplayLanguage(defLocale));
+ }
+ dialog.dismiss();
+ }
+
+ /**
+ * Remove the section of recent languages
+ */
+ private void hideRecentLanguagesSection() {
+ languageHistoryListView.setVisibility(View.GONE);
+ recentLanguagesTextView.setVisibility(View.GONE);
+ separator.setVisibility(View.GONE);
+ }
+
/**
* Changing the default app language with selected one and save it to SharedPreferences
*/
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt b/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt
index 447618dd4..faaff808f 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt
@@ -74,6 +74,13 @@ class LanguagesAdapter constructor(
return languageCodesList[position]
}
+ /**
+ * Provides name of a language from languages for a specific position
+ */
+ fun getLanguageName(position: Int): String {
+ return languageNamesList[position]
+ }
+
fun getIndexOfUserDefaultLocale(context: Context): Int {
return language.codes.indexOf(context.locale.language)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java
index 5a8d0ef6d..d87bf0ba9 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.upload;
import android.app.Dialog;
+import android.content.Intent;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -21,26 +22,39 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.material.textfield.TextInputLayout;
import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.contributions.MainActivity;
+import fr.free.nrw.commons.recentlanguages.Language;
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter;
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
import fr.free.nrw.commons.ui.PasteSensitiveTextInputEditText;
import fr.free.nrw.commons.utils.AbstractTextWatcher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import javax.inject.Inject;
import timber.log.Timber;
public class UploadMediaDetailAdapter extends RecyclerView.Adapter {
+ RecentLanguagesDao recentLanguagesDao;
+
private List uploadMediaDetails;
private Callback callback;
private EventListener eventListener;
private HashMap selectedLanguages;
private final String savedLanguageValue;
+ private TextView recentLanguagesTextView;
+ private View separator;
+ private ListView languageHistoryListView;
- public UploadMediaDetailAdapter(String savedLanguageValue) {
+ public UploadMediaDetailAdapter(String savedLanguageValue, RecentLanguagesDao recentLanguagesDao) {
uploadMediaDetails = new ArrayList<>();
selectedLanguages = new HashMap<>();
this.savedLanguageValue = savedLanguageValue;
+ this.recentLanguagesDao = recentLanguagesDao;
}
public UploadMediaDetailAdapter(final String savedLanguageValue,
@@ -183,7 +197,9 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter recentLanguages = recentLanguagesDao.getRecentLanguages();
+
+ LanguagesAdapter languagesAdapter = new LanguagesAdapter(
descriptionLanguages.getContext(),
selectedLanguages
);
@@ -200,6 +216,10 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter {
+ onRecentLanguageClicked(dialog, adapterView, position, description);
+ });
+
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView> adapterView, View view, int i,
@@ -230,6 +254,16 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter adapterView,
+ final int position, final UploadMediaDetail description) {
+ description.setSelectedLanguageIndex(position);
+ final String languageCode = ((RecentLanguagesAdapter) adapterView.getAdapter())
+ .getLanguageCode(position);
+ description.setLanguageCode(languageCode);
+ final String languageName = ((RecentLanguagesAdapter) adapterView.getAdapter())
+ .getLanguageName(position);
+ final boolean isExists = recentLanguagesDao.findRecentLanguage(languageCode);
+ if (isExists) {
+ recentLanguagesDao.deleteRecentLanguage(languageCode);
+ }
+ recentLanguagesDao.addRecentLanguage(new Language(languageName, languageCode));
+
+ selectedLanguages.remove(position);
+ selectedLanguages.put(position, languageCode);
+ ((RecentLanguagesAdapter) adapterView
+ .getAdapter()).setSelectedLangCode(languageCode);
+ Timber.d("Description language code is: %s", languageCode);
+ descriptionLanguages.setText(languageCode);
+ dialog.dismiss();
+ }
+
+ /**
+ * Hides recent languages section
+ */
+ private void hideRecentLanguagesSection() {
+ languageHistoryListView.setVisibility(View.GONE);
+ recentLanguagesTextView.setVisibility(View.GONE);
+ separator.setVisibility(View.GONE);
+ }
+
+ /**
+ * Set up recent languages section
+ *
+ * @param recentLanguages recently used languages
+ */
+ private void setUpRecentLanguagesSection(final List recentLanguages) {
+ if (recentLanguages.isEmpty()) {
+ languageHistoryListView.setVisibility(View.GONE);
+ recentLanguagesTextView.setVisibility(View.GONE);
+ separator.setVisibility(View.GONE);
+ } else {
+ if (recentLanguages.size() > 5) {
+ for (int i = recentLanguages.size()-1; i >=5; i--) {
+ recentLanguagesDao.deleteRecentLanguage(recentLanguages.get(i)
+ .getLanguageCode());
+ }
+ }
+ languageHistoryListView.setVisibility(View.VISIBLE);
+ recentLanguagesTextView.setVisibility(View.VISIBLE);
+ separator.setVisibility(View.VISIBLE);
+ final RecentLanguagesAdapter recentLanguagesAdapter
+ = new RecentLanguagesAdapter(
+ descriptionLanguages.getContext(),
+ recentLanguagesDao.getRecentLanguages(),
+ selectedLanguages);
+ languageHistoryListView.setAdapter(recentLanguagesAdapter);
+ }
+ }
}
public interface Callback {
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java
index dab8e415a..d4200e52d 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java
@@ -31,6 +31,7 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.filepicker.UploadableFile;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.nearby.Place;
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.ImageCoordinates;
import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
@@ -90,6 +91,9 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@Named("default_preferences")
JsonKvStore defaultKvStore;
+ @Inject
+ RecentLanguagesDao recentLanguagesDao;
+
private UploadableFile uploadableFile;
private Place place;
@@ -201,7 +205,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
* init the description recycler veiw and caption recyclerview
*/
private void initRecyclerView() {
- uploadMediaDetailAdapter = new UploadMediaDetailAdapter(defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""));
+ uploadMediaDetailAdapter = new UploadMediaDetailAdapter(defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao);
uploadMediaDetailAdapter.setCallback(this::showInfoAlert);
uploadMediaDetailAdapter.setEventListener(this);
rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
diff --git a/app/src/main/res/layout/dialog_select_language.xml b/app/src/main/res/layout/dialog_select_language.xml
index a2314aa55..776a2e042 100644
--- a/app/src/main/res/layout/dialog_select_language.xml
+++ b/app/src/main/res/layout/dialog_select_language.xml
@@ -18,6 +18,49 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@+id/all_languages" />
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8b1c21544..6ff9b46a4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -293,6 +293,7 @@
Search
Recent searches:
Recently searched queries
+ Recent language queries
Error occurred while loading categories.
Error occurred while loading depictions.
Media
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapterUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapterUnitTest.kt
new file mode 100644
index 000000000..0f62e6125
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesAdapterUnitTest.kt
@@ -0,0 +1,114 @@
+package fr.free.nrw.commons.recentlanguages
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import com.nhaarman.mockitokotlin2.whenever
+import fr.free.nrw.commons.TestCommonsApplication
+import kotlinx.android.synthetic.main.row_item_languages_spinner.view.*
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import java.lang.reflect.Field
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+class RecentLanguagesAdapterUnitTest {
+
+ private lateinit var adapter: RecentLanguagesAdapter
+ private lateinit var languages: List
+
+ @Mock
+ private lateinit var context: Context
+
+ @Mock
+ private lateinit var viewGroup: ViewGroup
+
+ @Mock
+ private lateinit var convertView: View
+
+ @Mock
+ private lateinit var textView: TextView
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ languages = listOf(
+ Language("English", "en"),
+ Language("Bengali", "bn")
+ )
+ adapter = RecentLanguagesAdapter(context, languages, hashMapOf(1 to "en"))
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun checkAdapterNotNull() {
+ Assert.assertNotNull(adapter)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testIsEnabled() {
+ val list = languages
+ val recentLanguagesAdapter: Field =
+ RecentLanguagesAdapter::class.java.getDeclaredField("recentLanguages")
+ recentLanguagesAdapter.isAccessible = true
+ recentLanguagesAdapter.set(adapter, list)
+ Assert.assertEquals(adapter.isEnabled(0), false)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testGetCount() {
+ val list = languages
+ val recentLanguagesAdapter: Field =
+ RecentLanguagesAdapter::class.java.getDeclaredField("recentLanguages")
+ recentLanguagesAdapter.isAccessible = true
+ recentLanguagesAdapter.set(adapter, list)
+ Assert.assertEquals(adapter.count, list.size)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testGetLanguageName() {
+ val list = languages
+ val recentLanguagesAdapter: Field =
+ RecentLanguagesAdapter::class.java.getDeclaredField("recentLanguages")
+ recentLanguagesAdapter.isAccessible = true
+ recentLanguagesAdapter.set(adapter, list)
+ val languageName = list[0].languageName
+ Assert.assertEquals(adapter.getLanguageName(0), languageName)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testGetView() {
+ val list = languages
+ whenever(convertView.tv_language).thenReturn(textView)
+ val recentLanguagesAdapter: Field =
+ RecentLanguagesAdapter::class.java.getDeclaredField("recentLanguages")
+ recentLanguagesAdapter.isAccessible = true
+ recentLanguagesAdapter.set(adapter, list)
+ Assert.assertEquals(adapter.getView(0, convertView, viewGroup), convertView)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testGetLanguageCode() {
+ val list = languages
+ val recentLanguagesAdapter: Field =
+ RecentLanguagesAdapter::class.java.getDeclaredField("recentLanguages")
+ recentLanguagesAdapter.isAccessible = true
+ recentLanguagesAdapter.set(adapter, list)
+ val languageCode = list[0].languageCode
+ Assert.assertEquals(adapter.getLanguageCode(0), languageCode)
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProviderUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProviderUnitTest.kt
new file mode 100644
index 000000000..beaa5671d
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesContentProviderUnitTest.kt
@@ -0,0 +1,37 @@
+package fr.free.nrw.commons.recentlanguages
+
+import android.net.Uri
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.data.DBOpenHelper
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.powermock.reflect.Whitebox
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+class RecentLanguagesContentProviderUnitTest {
+
+ private lateinit var contentProvider: RecentLanguagesContentProvider
+
+ @Mock
+ lateinit var dbOpenHelper: DBOpenHelper
+
+ @Before
+ fun setUp(){
+ MockitoAnnotations.initMocks(this)
+ contentProvider = RecentLanguagesContentProvider()
+ Whitebox.setInternalState(contentProvider, "dbOpenHelper", dbOpenHelper)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testGetType() {
+ contentProvider.getType(mock(Uri::class.java))
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt
new file mode 100644
index 000000000..122f2e49c
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt
@@ -0,0 +1,227 @@
+package fr.free.nrw.commons.recentlanguages
+
+import android.content.ContentProviderClient
+import android.content.ContentValues
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.database.sqlite.SQLiteDatabase
+import android.os.RemoteException
+import com.nhaarman.mockitokotlin2.*
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao.Table.*
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+class RecentLanguagesDaoUnitTest {
+
+ private val columns = arrayOf(
+ COLUMN_NAME,
+ COLUMN_CODE
+ )
+
+ private val client: ContentProviderClient = mock()
+ private val database: SQLiteDatabase = mock()
+ private val captor = argumentCaptor()
+
+ private lateinit var testObject: RecentLanguagesDao
+ private lateinit var exampleLanguage: Language
+
+ /**
+ * Set up Test Language and RecentLanguagesDao
+ */
+ @Before
+ fun setUp() {
+ exampleLanguage = Language("English", "en")
+ testObject = RecentLanguagesDao { client }
+ }
+
+ @Test
+ fun createTable() {
+ onCreate(database)
+ verify(database).execSQL(CREATE_TABLE_STATEMENT)
+ }
+
+ @Test
+ fun deleteTable() {
+ onDelete(database)
+ inOrder(database) {
+ verify(database).execSQL(DROP_TABLE_STATEMENT)
+ }
+ }
+
+ @Test
+ fun createFromCursor() {
+ createCursor(1).let { cursor ->
+ cursor.moveToFirst()
+ testObject.fromCursor(cursor).let {
+ Assert.assertEquals("languageName", it.languageName)
+ Assert.assertEquals("languageCode", it.languageCode)
+ }
+ }
+ }
+
+ @Test
+ fun testGetRecentLanguages() {
+ whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull()))
+ .thenReturn(createCursor(14))
+
+ val result = testObject.recentLanguages
+
+ Assert.assertEquals(14, (result.size))
+
+ }
+
+ @Test(expected = RuntimeException::class)
+ fun getGetRecentLanguagesTranslatesExceptions() {
+ whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(
+ RemoteException("")
+ )
+ testObject.recentLanguages
+ }
+
+ @Test
+ fun getGetRecentLanguagesReturnsEmptyList_emptyCursor() {
+ whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull()))
+ .thenReturn(createCursor(0))
+ Assert.assertTrue(testObject.recentLanguages.isEmpty())
+ }
+
+ @Test
+ fun getGetRecentLanguagesReturnsEmptyList_nullCursor() {
+ whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null)
+ Assert.assertTrue(testObject.recentLanguages.isEmpty())
+ }
+
+ @Test
+ fun cursorsAreClosedAfterGetRecentLanguages() {
+ val mockCursor: Cursor = mock()
+ whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor)
+ whenever(mockCursor.moveToFirst()).thenReturn(false)
+
+ testObject.recentLanguages
+
+ verify(mockCursor).close()
+ }
+
+ @Test
+ fun findExistingLanguage() {
+ whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1))
+ Assert.assertTrue(testObject.findRecentLanguage(exampleLanguage.languageCode))
+ }
+
+ @Test(expected = RuntimeException::class)
+ fun findLanguageTranslatesExceptions() {
+ whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(
+ RemoteException("")
+ )
+ testObject.findRecentLanguage(exampleLanguage.languageCode)
+ }
+
+ @Test
+ fun findNotExistingLanguageReturnsNull_emptyCursor() {
+ whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(0))
+ Assert.assertFalse(testObject.findRecentLanguage(exampleLanguage.languageCode))
+ }
+
+ @Test
+ fun findNotExistingLanguageReturnsNull_nullCursor() {
+ whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null)
+ Assert.assertFalse(testObject.findRecentLanguage(exampleLanguage.languageCode))
+ }
+
+ @Test
+ fun cursorsAreClosedAfterFindLanguageQuery() {
+ val mockCursor: Cursor = mock()
+ whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(mockCursor)
+ whenever(mockCursor.moveToFirst()).thenReturn(false)
+
+ testObject.findRecentLanguage(exampleLanguage.languageCode)
+
+ verify(mockCursor).close()
+ }
+
+ @Test
+ fun migrateTableVersionFrom_v1_to_v2() {
+ onUpdate(database, 1, 2)
+ // Table didnt exist before v7
+ verifyZeroInteractions(database)
+ }
+
+ @Test
+ fun migrateTableVersionFrom_v2_to_v3() {
+ onUpdate(database, 2, 3)
+ // Table didnt exist before v7
+ verifyZeroInteractions(database)
+ }
+
+ @Test
+ fun migrateTableVersionFrom_v3_to_v4() {
+ onUpdate(database, 3, 4)
+ // Table didnt exist before v7
+ verifyZeroInteractions(database)
+ }
+
+ @Test
+ fun migrateTableVersionFrom_v4_to_v5() {
+ onUpdate(database, 4, 5)
+ // Table didnt exist before v7
+ verifyZeroInteractions(database)
+ }
+
+ @Test
+ fun migrateTableVersionFrom_v5_to_v6() {
+ onUpdate(database, 5, 6)
+ // Table didnt exist before v7
+ verifyZeroInteractions(database)
+ }
+
+ @Test
+ fun migrateTableVersionFrom_v6_to_v7() {
+ onUpdate(database, 6, 7)
+ verify(database).execSQL(CREATE_TABLE_STATEMENT)
+ }
+
+ @Test
+ fun migrateTableVersionFrom_v7_to_v8() {
+ onUpdate(database, 7, 8)
+ // Table didnt change in version 8
+ verifyZeroInteractions(database)
+ }
+
+ @Test
+ fun testAddNewLanguage() {
+ testObject.addRecentLanguage(exampleLanguage)
+
+ verify(client).insert(eq(RecentLanguagesContentProvider.BASE_URI), captor.capture())
+ captor.firstValue.let { cv ->
+ Assert.assertEquals(2, cv.size())
+ Assert.assertEquals(
+ exampleLanguage.languageName,
+ cv.getAsString(COLUMN_NAME)
+ )
+ Assert.assertEquals(
+ exampleLanguage.languageCode,
+ cv.getAsString(COLUMN_CODE)
+ )
+ }
+ }
+
+ @Test
+ fun testDeleteLanguage() {
+ testObject.addRecentLanguage(exampleLanguage)
+ testObject.deleteRecentLanguage(exampleLanguage.languageCode)
+ }
+
+ private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
+
+ for (i in 0 until rowCount) {
+ addRow(listOf("languageName", "languageCode"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/settings/SettingsFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/settings/SettingsFragmentUnitTests.kt
index 9d1b7172b..2fc6fe937 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/settings/SettingsFragmentUnitTests.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/settings/SettingsFragmentUnitTests.kt
@@ -1,16 +1,32 @@
package fr.free.nrw.commons.settings
+import android.app.Dialog
import android.content.Context
import android.os.Looper
import android.view.LayoutInflater
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ListView
+import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.recentlanguages.Language
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
+import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+import org.powermock.reflect.Whitebox
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
@@ -29,6 +45,21 @@ class SettingsFragmentUnitTests {
private lateinit var layoutInflater: LayoutInflater
private lateinit var context: Context
+ @Mock
+ private lateinit var recentLanguagesDao: RecentLanguagesDao
+
+ @Mock
+ private lateinit var recentLanguagesTextView: TextView
+
+ @Mock
+ private lateinit var separator: View
+
+ @Mock
+ private lateinit var languageHistoryListView: ListView
+
+ @Mock
+ private lateinit var adapterView: AdapterView
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -42,6 +73,13 @@ class SettingsFragmentUnitTests {
fragmentTransaction.commitNowAllowingStateLoss()
layoutInflater = LayoutInflater.from(activity)
+
+ Whitebox.setInternalState(fragment, "recentLanguagesDao", recentLanguagesDao)
+ Whitebox.setInternalState(fragment, "recentLanguagesTextView",
+ recentLanguagesTextView)
+ Whitebox.setInternalState(fragment, "separator", separator)
+ Whitebox.setInternalState(fragment, "languageHistoryListView",
+ languageHistoryListView)
}
@Test
@@ -107,6 +145,41 @@ class SettingsFragmentUnitTests {
method.invoke(fragment, "", "appUiDefaultLanguagePref")
}
+ @Test
+ @Throws(Exception::class)
+ fun `Test prepareAppLanguages when recently used languages is empty`() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ val method: Method = SettingsFragment::class.java.getDeclaredMethod(
+ "prepareAppLanguages",
+ String::class.java
+ )
+ method.isAccessible = true
+ method.invoke(fragment, "appUiDefaultLanguagePref")
+ verify(recentLanguagesDao, times(1)).recentLanguages
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun `Test prepareAppLanguages when recently used languages is not empty`() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ whenever(recentLanguagesDao.recentLanguages)
+ .thenReturn(
+ mutableListOf(Language("English", "en"),
+ Language("English", "en"),
+ Language("English", "en"),
+ Language("English", "en"),
+ Language("English", "en"),
+ Language("English", "en"))
+ )
+ val method: Method = SettingsFragment::class.java.getDeclaredMethod(
+ "prepareAppLanguages",
+ String::class.java
+ )
+ method.isAccessible = true
+ method.invoke(fragment, "appUiDefaultLanguagePref")
+ verify(recentLanguagesDao, times(2)).recentLanguages
+ }
+
@Test
@Throws(Exception::class)
fun testSaveLanguageValueCase_descriptionDefaultLanguagePref() {
@@ -120,4 +193,77 @@ class SettingsFragmentUnitTests {
method.invoke(fragment, "", "descriptionDefaultLanguagePref")
}
+ @Test
+ @Throws(Exception::class)
+ fun testHideRecentLanguagesSection() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ val method: Method = SettingsFragment::class.java.getDeclaredMethod(
+ "hideRecentLanguagesSection"
+ )
+ method.isAccessible = true
+ method.invoke(fragment)
+ verify(recentLanguagesTextView, times(1)).visibility = any()
+ verify(separator, times(1)).visibility = any()
+ verify(languageHistoryListView, times(1)).visibility = any()
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testOnRecentLanguageClicked() {
+ whenever(recentLanguagesDao.findRecentLanguage(any()))
+ .thenReturn(true)
+ whenever(adapterView.adapter)
+ .thenReturn(RecentLanguagesAdapter(context,
+ listOf(Language("English", "en")),
+ hashMapOf()
+ )
+ )
+ val method: Method = SettingsFragment::class.java.getDeclaredMethod(
+ "onRecentLanguageClicked",
+ String::class.java,
+ Dialog::class.java,
+ AdapterView::class.java,
+ Int::class.java
+ )
+ method.isAccessible = true
+ method.invoke(fragment, "test", Mockito.mock(Dialog::class.java), adapterView, 0)
+ verify(recentLanguagesDao, times(1)).findRecentLanguage(any())
+ verify(adapterView, times(2)).adapter
+ }
+
+ @Test
+ fun `Test setUpRecentLanguagesSection when list is empty`() {
+ val method: Method = SettingsFragment::class.java.getDeclaredMethod(
+ "setUpRecentLanguagesSection",
+ List::class.java,
+ HashMap::class.java
+ )
+ method.isAccessible = true
+ method.invoke(fragment, emptyList(), hashMapOf())
+ verify(languageHistoryListView, times(1)).visibility = View.GONE
+ verify(separator, times(1)).visibility = View.GONE
+ verify(recentLanguagesTextView, times(1)).visibility = View.GONE
+ }
+
+ @Test
+ fun `Test setUpRecentLanguagesSection when list is not empty`() {
+ val method: Method = SettingsFragment::class.java.getDeclaredMethod(
+ "setUpRecentLanguagesSection",
+ List::class.java,
+ HashMap::class.java
+ )
+ method.isAccessible = true
+ method.invoke(fragment, listOf(
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn")
+ ), hashMapOf())
+ verify(languageHistoryListView, times(1)).visibility = View.VISIBLE
+ verify(separator, times(1)).visibility = View.VISIBLE
+ verify(recentLanguagesTextView, times(1)).visibility = View.VISIBLE
+ }
+
}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt
index c95269c8b..8886d4d85 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt
@@ -1,15 +1,27 @@
package fr.free.nrw.commons.upload
+import android.app.Dialog
import android.content.Context
+import android.view.View
+import android.widget.AdapterView
import android.widget.GridLayout
+import android.widget.ListView
+import android.widget.TextView
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.recentlanguages.Language
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter
+import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
+import fr.free.nrw.commons.settings.SettingsFragment
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.powermock.reflect.Whitebox
import org.robolectric.Robolectric
@@ -18,6 +30,7 @@ import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import java.lang.reflect.Field
+import java.lang.reflect.Method
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
@@ -36,15 +49,33 @@ class UploadMediaDetailAdapterUnitTest {
@Mock
private lateinit var eventListener: UploadMediaDetailAdapter.EventListener
+ @Mock
+ private lateinit var recentLanguagesDao: RecentLanguagesDao
+
+ @Mock
+ private lateinit var textView: TextView
+
+ @Mock
+ private lateinit var view: View
+
+ @Mock
+ private lateinit var listView: ListView
+
+ @Mock
+ private lateinit var adapterView: AdapterView
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
uploadMediaDetails = mutableListOf(uploadMediaDetail, uploadMediaDetail)
activity = Robolectric.buildActivity(UploadActivity::class.java).get()
- adapter = UploadMediaDetailAdapter("")
+ adapter = UploadMediaDetailAdapter("", recentLanguagesDao)
context = RuntimeEnvironment.application.applicationContext
Whitebox.setInternalState(adapter, "uploadMediaDetails", uploadMediaDetails)
Whitebox.setInternalState(adapter, "eventListener", eventListener)
+ Whitebox.setInternalState(adapter, "recentLanguagesTextView", textView)
+ Whitebox.setInternalState(adapter, "separator", view)
+ Whitebox.setInternalState(adapter, "languageHistoryListView", listView)
viewHolder = adapter.onCreateViewHolder(GridLayout(activity), 0)
}
@@ -145,4 +176,75 @@ class UploadMediaDetailAdapterUnitTest {
verify(uploadMediaDetail).isManuallyAdded
}
+ @Test
+ fun testHideRecentLanguagesSection() {
+ val method: Method = UploadMediaDetailAdapter.ViewHolder::class.java.getDeclaredMethod(
+ "hideRecentLanguagesSection"
+ )
+ method.isAccessible = true
+ method.invoke(viewHolder)
+ verify(listView, times(1)).visibility = View.GONE
+ verify(view, times(1)).visibility = View.GONE
+ verify(textView, times(1)).visibility = View.GONE
+ }
+
+ @Test
+ fun `Test setUpRecentLanguagesSection when list is empty`() {
+ val method: Method = UploadMediaDetailAdapter.ViewHolder::class.java.getDeclaredMethod(
+ "setUpRecentLanguagesSection",
+ List::class.java
+ )
+ method.isAccessible = true
+ method.invoke(viewHolder, emptyList())
+ verify(listView, times(1)).visibility = View.GONE
+ verify(view, times(1)).visibility = View.GONE
+ verify(textView, times(1)).visibility = View.GONE
+ }
+
+ @Test
+ fun `Test setUpRecentLanguagesSection when list is not empty`() {
+ val method: Method = UploadMediaDetailAdapter.ViewHolder::class.java.getDeclaredMethod(
+ "setUpRecentLanguagesSection",
+ List::class.java
+ )
+ method.isAccessible = true
+ method.invoke(viewHolder, listOf(
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn"),
+ Language("Bengali", "bn")
+ ))
+ verify(listView, times(1)).visibility = View.VISIBLE
+ verify(view, times(1)).visibility = View.VISIBLE
+ verify(textView, times(1)).visibility = View.VISIBLE
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testOnRecentLanguageClicked() {
+ whenever(recentLanguagesDao.findRecentLanguage(any()))
+ .thenReturn(true)
+ whenever(adapterView.adapter)
+ .thenReturn(
+ RecentLanguagesAdapter(context,
+ listOf(Language("English", "en")),
+ hashMapOf()
+ )
+ )
+ val method: Method = UploadMediaDetailAdapter.ViewHolder::class.java.getDeclaredMethod(
+ "onRecentLanguageClicked",
+ Dialog::class.java,
+ AdapterView::class.java,
+ Int::class.java,
+ UploadMediaDetail::class.java
+ )
+ method.isAccessible = true
+ method.invoke(viewHolder, Mockito.mock(Dialog::class.java), adapterView, 0,
+ Mockito.mock(UploadMediaDetail::class.java))
+ verify(recentLanguagesDao, times(1)).findRecentLanguage(any())
+ verify(adapterView, times(3)).adapter
+ }
+
}
\ No newline at end of file