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