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
This commit is contained in:
Ayan Sarkar 2022-03-23 13:03:54 +05:30 committed by GitHub
parent 85bdcd5a7a
commit 4194409cd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1312 additions and 9 deletions

View file

@ -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\""

View file

@ -194,6 +194,13 @@
android:label="@string/provider_searches"
android:syncable="false" />
<provider
android:name=".recentlanguages.RecentLanguagesContentProvider"
android:authorities="${applicationId}.recentlanguages.contentprovider"
android:exported="false"
android:label="@string/provider_recent_languages"
android:syncable="false" />
<provider
android:name=".bookmarks.pictures.BookmarkPicturesContentProvider"
android:authorities="${applicationId}.bookmarks.contentprovider"

View file

@ -11,6 +11,7 @@ import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryDao;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
public class DBOpenHelper extends SQLiteOpenHelper {
@ -34,6 +35,7 @@ public class DBOpenHelper extends SQLiteOpenHelper {
BookmarkLocationsDao.Table.onCreate(sqLiteDatabase);
BookmarkItemsDao.Table.onCreate(sqLiteDatabase);
RecentSearchesDao.Table.onCreate(sqLiteDatabase);
RecentLanguagesDao.Table.onCreate(sqLiteDatabase);
}
@Override
@ -43,6 +45,7 @@ public class DBOpenHelper extends SQLiteOpenHelper {
BookmarkLocationsDao.Table.onUpdate(sqLiteDatabase, from, to);
BookmarkItemsDao.Table.onUpdate(sqLiteDatabase, from, to);
RecentSearchesDao.Table.onUpdate(sqLiteDatabase, from, to);
RecentLanguagesDao.Table.onUpdate(sqLiteDatabase, from, to);
deleteTable(sqLiteDatabase,CONTRIBUTIONS_TABLE);
}

View file

@ -169,6 +169,19 @@ public class CommonsApplicationModule {
return context.getContentResolver().acquireContentProviderClient(BuildConfig.BOOKMARK_ITEMS_AUTHORITY);
}
/**
* This method is used to provide instance of RecentLanguagesContentProvider
* which provides content of recent used languages from database
* @param context Context
* @return returns RecentLanguagesContentProvider
*/
@Provides
@Named("recent_languages")
public ContentProviderClient provideRecentLanguagesContentProviderClient(final Context context) {
return context.getContentResolver()
.acquireContentProviderClient(BuildConfig.RECENT_LANGUAGE_AUTHORITY);
}
/**
* Provides a Json store instance(JsonKvStore) which keeps
* the provided Gson in it's instance

View file

@ -7,6 +7,7 @@ import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
import fr.free.nrw.commons.category.CategoryContentProvider;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider;
import fr.free.nrw.commons.recentlanguages.RecentLanguagesContentProvider;
/**
* This Class Represents the Module for dependency injection (using dagger)
@ -31,4 +32,7 @@ public abstract class ContentProviderBuilderModule {
@ContributesAndroidInjector
abstract BookmarkItemsContentProvider bindBookmarkItemContentProvider();
@ContributesAndroidInjector
abstract RecentLanguagesContentProvider bindRecentLanguagesContentProvider();
}

View file

@ -0,0 +1,3 @@
package fr.free.nrw.commons.recentlanguages
data class Language(val languageName: String, val languageCode: String)

View file

@ -0,0 +1,68 @@
package fr.free.nrw.commons.recentlanguages
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import fr.free.nrw.commons.R
import fr.free.nrw.commons.utils.LangCodeUtils
import kotlinx.android.synthetic.main.row_item_languages_spinner.view.*
import org.apache.commons.lang3.StringUtils
import java.util.HashMap
/**
* Array adapter for recent languages
*/
class RecentLanguagesAdapter constructor(
context: Context,
var recentLanguages: List<Language>,
private val selectedLanguages: HashMap<*, String>
) : ArrayAdapter<String?>(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
}
}

View file

@ -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;
}
}

View file

@ -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<ContentProviderClient> clientProvider;
@Inject
public RecentLanguagesDao
(@Named("recent_languages") final Provider<ContentProviderClient> clientProvider) {
this.clientProvider = clientProvider;
}
/**
* Find all persisted recently used languages on database
* @return list of recently used languages
*/
public List<Language> getRecentLanguages() {
final List<Language> 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;
}
}
}
}

View file

@ -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<Language> recentLanguages = recentLanguagesDao.getRecentLanguages();
HashMap<Integer, String> 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<Language> recentLanguages,
HashMap<Integer, String> 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
*/

View file

@ -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)
}

View file

@ -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<UploadMediaDetailAdapter.ViewHolder> {
RecentLanguagesDao recentLanguagesDao;
private List<UploadMediaDetail> uploadMediaDetails;
private Callback callback;
private EventListener eventListener;
private HashMap<Integer, String> 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,6 +197,8 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
private void initLanguage(int position, UploadMediaDetail description) {
final List<Language> recentLanguages = recentLanguagesDao.getRecentLanguages();
LanguagesAdapter languagesAdapter = new LanguagesAdapter(
descriptionLanguages.getContext(),
selectedLanguages
@ -200,6 +216,10 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
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);
listView.setAdapter(languagesAdapter);
@ -207,7 +227,7 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
int i2) {
hideRecentLanguagesSection();
}
@Override
@ -222,6 +242,10 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
}
});
languageHistoryListView.setOnItemClickListener((adapterView, view1, position, id) -> {
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<UploadMediaDe
String languageCode = ((LanguagesAdapter) adapterView.getAdapter())
.getLanguageCode(i);
description.setLanguageCode(languageCode);
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));
selectedLanguages.remove(position);
selectedLanguages.put(position, languageCode);
((LanguagesAdapter) adapterView
@ -300,6 +334,70 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
selectedLanguages.put(position, description.getLanguageCode());
}
}
/**
* Handles click event for recent language section
*/
private void onRecentLanguageClicked(final Dialog dialog, final AdapterView<?> 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<Language> 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 {

View file

@ -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()));

View file

@ -18,6 +18,49 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"></EditText>
<TextView
android:id="@+id/recent_searches"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Recent Searches"
app:layout_constraintTop_toBottomOf="@id/search_language"
app:layout_constraintEnd_toEndOf="@+id/language_history_list"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent" />
<ListView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:id="@+id/language_history_list"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recent_searches"
app:layout_constraintBottom_toTopOf="@id/separator"/>
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black"
android:layout_marginTop="10dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/language_history_list" />
<TextView
android:id="@+id/all_languages"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="All Languages"
app:layout_constraintTop_toBottomOf="@id/separator"
app:layout_constraintEnd_toEndOf="@+id/language_history_list"
android:layout_margin="8dp"
app:layout_constraintStart_toStartOf="parent" />
<ListView
android:layout_width="match_parent"
android:layout_height="0dp"
@ -26,6 +69,6 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_language" />
app:layout_constraintTop_toBottomOf="@+id/all_languages" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -293,6 +293,7 @@
<string name="title_activity_search">Search</string>
<string name="search_recent_header">Recent searches:</string>
<string name="provider_searches">Recently searched queries</string>
<string name="provider_recent_languages">Recent language queries</string>
<string name="error_loading_categories">Error occurred while loading categories.</string>
<string name="error_loading_depictions">Error occurred while loading depictions.</string>
<string name="search_tab_title_media">Media</string>

View file

@ -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<Language>
@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)
}
}

View file

@ -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))
}
}

View file

@ -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<ContentValues>()
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"))
}
}
}

View file

@ -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<RecentLanguagesAdapter>
@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<String,String>()
)
)
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<Language>(), hashMapOf<Int,String>())
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<Int,String>())
verify(languageHistoryListView, times(1)).visibility = View.VISIBLE
verify(separator, times(1)).visibility = View.VISIBLE
verify(recentLanguagesTextView, times(1)).visibility = View.VISIBLE
}
}

View file

@ -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<RecentLanguagesAdapter>
@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<Language>())
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<String,String>()
)
)
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
}
}