Feature/localised image descriptions (#1634)

* wip

* Changes for adding descriptions in multipe languages[issue #1501]

* Added callback for the adapter

* Codacy suggested changes

* Sort the languages in the spinner in alphabetical order

* scroll view nested scrolling enabled false

* Nested scrolling enabled false [Allow rv to expand]

* rebased to master, resolved conflicts

* replaced setCompoundDrawables with setCompoundDrawablesWithIntrinsicBounds [the former dint used to work on all devices]

*     replaced setCompoundDrawables with setCompoundDrawablesWithIntrinsicBounds [the former dint used to work on all devices]
This commit is contained in:
Ashish Kumar 2018-08-03 18:09:37 +05:30 committed by Vivek Maskara
parent e4c518ccce
commit 12a83da3a2
12 changed files with 622 additions and 122 deletions

View file

@ -60,6 +60,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private boolean isCategoryImage;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
private int index;
private Locale locale;
public static MediaDetailFragment forMedia(int index, boolean editable, boolean isCategoryImage) {
MediaDetailFragment mf = new MediaDetailFragment();
@ -198,6 +199,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
}
};
view.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
locale = getResources().getConfiguration().locale;
return view;
}
@ -455,7 +457,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private String prettyDescription(Media media) {
// @todo use UI language when multilingual descs are available
String desc = media.getDescription("en").trim();
String desc = media.getDescription(locale.getLanguage()).trim();
if (desc.equals("")) {
return getString(R.string.detail_description_empty);
} else {

View file

@ -0,0 +1,57 @@
package fr.free.nrw.commons.upload;
import android.text.TextUtils;
import android.util.TimeUtils;
class Description {
private String languageId;
private String languageDisplayText;
private String descriptionText;
private boolean set;
private int selectedLanguageIndex = -1;
public String getLanguageId() {
return languageId;
}
public void setLanguageId(String languageId) {
this.languageId = languageId;
}
public String getLanguageDisplayText() {
return languageDisplayText;
}
public void setLanguageDisplayText(String languageDisplayText) {
this.languageDisplayText = languageDisplayText;
}
public String getDescriptionText() {
return descriptionText;
}
public void setDescriptionText(String descriptionText) {
this.descriptionText = descriptionText;
if (!TextUtils.isEmpty(descriptionText)) {
set = true;
}
}
public boolean isSet() {
return set;
}
public void setSet(boolean set) {
this.set = set;
}
public int getSelectedLanguageIndex() {
return selectedLanguageIndex;
}
public void setSelectedLanguageIndex(int selectedLanguageIndex) {
this.selectedLanguageIndex = selectedLanguageIndex;
}
}

View file

@ -0,0 +1,231 @@
package fr.free.nrw.commons.upload;
import static android.view.MotionEvent.ACTION_UP;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.AppCompatSpinner;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.EditText;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnTouch;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.utils.ViewUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewHolder> {
List<Description> descriptions;
List<Language> languages;
private Context context;
private Callback callback;
public DescriptionsAdapter() {
descriptions = new ArrayList<>();
descriptions.add(new Description());
languages = new ArrayList<>();
}
public void setCallback(Callback callback) {
this.callback = callback;
}
public void setDescriptions(List<Description> descriptions) {
this.descriptions = descriptions;
notifyDataSetChanged();
}
public void setLanguages(List<Language> languages) {
this.languages = languages;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_item_description, parent, false);
context = parent.getContext();
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.init(position);
}
@Override
public int getItemCount() {
return descriptions.size();
}
public List<Description> getDescriptions() {
return descriptions;
}
public void addDescription(Description description) {
this.descriptions.add(description);
notifyItemInserted(descriptions.size() - 1);
}
public class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.spinner_description_languages)
AppCompatSpinner spinnerDescriptionLanguages;
@BindView(R.id.et_description_text)
EditText etDescriptionText;
private View view;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
this.view = itemView;
}
public void init(int position) {
Description description = descriptions.get(position);
if (!TextUtils.isEmpty(description.getDescriptionText())) {
etDescriptionText.setText(description.getDescriptionText());
} else {
etDescriptionText.setText("");
}
Drawable drawableRight = context.getResources()
.getDrawable(R.drawable.mapbox_info_icon_default);
if (position != 0) {
etDescriptionText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
} else {
etDescriptionText.setCompoundDrawablesWithIntrinsicBounds(null, null, drawableRight, null);
}
etDescriptionText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
description.setDescriptionText(editable.toString());
}
});
etDescriptionText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
ViewUtil.hideKeyboard(v);
}
});
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(context,
R.layout.row_item_languages_spinner);
Collections.sort(languages, (language, t1) -> language.getLocale().getDisplayLanguage()
.compareTo(t1.getLocale().getDisplayLanguage().toString()));
languagesAdapter.setLanguages(languages);
languagesAdapter.notifyDataSetChanged();
spinnerDescriptionLanguages.setAdapter(languagesAdapter);
if (description.getSelectedLanguageIndex() == -1) {
if (position == 0) {
int defaultLocaleIndex = getIndexOfUserDefaultLocale();
spinnerDescriptionLanguages.setSelection(defaultLocaleIndex);
} else {
spinnerDescriptionLanguages.setSelection(0);
}
} else {
spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex());
}
languages.get(spinnerDescriptionLanguages.getSelectedItemPosition()).setSet(true);
//TODO do it the butterknife way
spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
long l) {
//TODO handle case when user tries to select an already selected language
updateDescriptionBasedOnSelectedLanguageIndex(description, position);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
@OnTouch(R.id.et_description_text)
boolean descriptionInfo(View view, MotionEvent motionEvent) {
if (getAdapterPosition() == 0) {
//Description info is visible only for the first item
final int value;
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
value = etDescriptionText.getRight() - etDescriptionText
.getCompoundDrawables()[2]
.getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
callback.showAlert(R.string.media_detail_description,
R.string.description_info);
return true;
}
} else {
value = etDescriptionText.getLeft() + etDescriptionText
.getCompoundDrawables()[0]
.getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
callback.showAlert(R.string.media_detail_description,
R.string.description_info);
return true;
}
}
}
return false;
}
}
private int getIndexOfUserDefaultLocale() {
for (int i = 0; i < languages.size(); i++) {
if (languages.get(i).getLocale()
.equals(context.getResources().getConfiguration().locale)) {
return i;
}
}
return 0;
}
private void updateDescriptionBasedOnSelectedLanguageIndex(Description description,
int position) {
Language language = languages.get(position);
Locale locale = language.getLocale();
description.setSelectedLanguageIndex(position);
description.setLanguageDisplayText(locale.getDisplayName());
description.setLanguageId(locale.getLanguage());
}
public interface Callback {
void showAlert(int mediaDetailDescription, int descriptionInfo);
}
}

View file

@ -0,0 +1,29 @@
package fr.free.nrw.commons.upload;
import java.util.Locale;
class Language {
private Locale locale;
private boolean isSet = false;
public Language(Locale locale) {
this.locale = locale;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public boolean isSet() {
return isSet;
}
public void setSet(boolean set) {
isSet = set;
}
}

View file

@ -481,7 +481,7 @@ public class ShareActivity
CurrentAnimator.cancel();
}
isZoom = true;
ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit));
ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit));
closeFABMenu();
mainFab.setVisibility(View.GONE);

View file

@ -10,6 +10,8 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
@ -30,9 +32,16 @@ import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import fr.free.nrw.commons.upload.DescriptionsAdapter.Callback;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
@ -53,7 +62,7 @@ import static android.view.MotionEvent.ACTION_UP;
public class SingleUploadFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.titleEdit) EditText titleEdit;
@BindView(R.id.descEdit) EditText descEdit;
@BindView(R.id.rv_descriptions) RecyclerView rvDescriptions;
@BindView(R.id.titleDescButton) Button titleDescButton;
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
@ -65,6 +74,7 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
private String license;
private OnUploadActionInitiated uploadActionInitiatedHandler;
private TitleTextWatcher textWatcher = new TitleTextWatcher();
private DescriptionsAdapter descriptionsAdapter;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
@ -82,35 +92,60 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
return false;
}
String title = titleEdit.getText().toString().trim();
String desc = descEdit.getText().toString().trim();
String title = titleEdit.getText().toString();
String descriptionsInVariousLanguages = getDescriptionsInAppropriateFormat();
//Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these
prefs.edit()
.putString("Title", title)
.putString("Desc", desc)
.putString("Desc", new Gson().toJson(descriptionsAdapter
.getDescriptions()))//Description, now is not just a string, its a list of description objects
.apply();
uploadActionInitiatedHandler.uploadActionInitiated(title, desc);
uploadActionInitiatedHandler
.uploadActionInitiated(title, descriptionsInVariousLanguages);
return true;
}
return super.onOptionsItemSelected(item);
}
private String getDescriptionsInAppropriateFormat() {
List<Description> descriptions = descriptionsAdapter.getDescriptions();
StringBuilder descriptionsInAppropriateFormat = new StringBuilder();
for (Description description : descriptions) {
String individualDescription = String.format("{{%s|1=%s}}", description.getLanguageId(),
description.getDescriptionText());
descriptionsInAppropriateFormat.append(individualDescription);
}
return descriptionsInAppropriateFormat.toString();
}
private List<Description> getDescriptions() {
List<Description> descriptions = descriptionsAdapter.getDescriptions();
return descriptions;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
ButterKnife.bind(this, rootView);
initRecyclerView();
Intent activityIntent = getActivity().getIntent();
if (activityIntent.hasExtra("title")) {
titleEdit.setText(activityIntent.getStringExtra("title"));
}
if (activityIntent.hasExtra("description")) {
descEdit.setText(activityIntent.getStringExtra("description"));
if (activityIntent.hasExtra("description") && descriptionsAdapter.getDescriptions() != null
&& descriptionsAdapter.getDescriptions().size() > 0) {
descriptionsAdapter.getDescriptions().get(0)
.setDescriptionText(activityIntent.getStringExtra("description"));
descriptionsAdapter.notifyItemChanged(0);
}
ArrayList<String> licenseItems = new ArrayList<>();
licenseItems.add(getString(R.string.license_name_cc0));
licenseItems.add(getString(R.string.license_name_cc_by));
@ -129,7 +164,11 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
String imageCats = directPrefs.getString("Category", "");
Timber.d("Image title: " + imageTitle + ", image desc: " + imageDesc + ", image categories: " + imageCats);
titleEdit.setText(imageTitle);
descEdit.setText(imageDesc);
if (descriptionsAdapter.getDescriptions() != null
&& descriptionsAdapter.getDescriptions().size() > 0) {
descriptionsAdapter.getDescriptions().get(0).setDescriptionText(imageDesc);
descriptionsAdapter.notifyItemChanged(0);
}
}
// check if this is the first time we have uploaded
@ -170,17 +209,29 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
}
});
descEdit.setOnFocusChangeListener((v, hasFocus) -> {
if(!hasFocus){
ViewUtil.hideKeyboard(v);
}
});
setLicenseSummary(license);
return rootView;
}
private void initRecyclerView() {
descriptionsAdapter = new DescriptionsAdapter();
descriptionsAdapter.setCallback((mediaDetailDescription, descriptionInfo) -> showInfoAlert(mediaDetailDescription,descriptionInfo));
descriptionsAdapter.setLanguages(getLocaleSupportedByDevice());
rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
rvDescriptions.setAdapter(descriptionsAdapter);
}
private List<Language> getLocaleSupportedByDevice() {
List<Language> languages = new ArrayList<>();
Locale[] localesArray = Locale.getAvailableLocales();
List<Locale> locales = Arrays.asList(localesArray);
for (Locale locale : locales) {
languages.add(new Language(locale));
}
return languages;
}
@Override
public void onDestroyView() {
titleEdit.removeTextChangedListener(textWatcher);
@ -224,11 +275,16 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
void setTitleDescButton() {
//Retrieve last title and desc entered
String title = prefs.getString("Title", "");
String desc = prefs.getString("Desc", "");
Timber.d("Title: %s, Desc: %s", title, desc);
String descriptionJson = prefs.getString("Desc", "");
Timber.d("Title: %s, Desc: %s", title, descriptionJson);
titleEdit.setText(title);
descEdit.setText(desc);
Type typeOfDest = new TypeToken<List<Description>>() {
}.getType();
List<Description> descriptions = new Gson().fromJson(descriptionJson, typeOfDest);
descriptionsAdapter.setDescriptions(descriptions);
}
/**
@ -254,26 +310,6 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
return false;
}
@OnTouch(R.id.descEdit)
boolean descriptionInfo(View view, MotionEvent motionEvent) {
final int value;
if (ViewCompat.getLayoutDirection(getView()) == ViewCompat.LAYOUT_DIRECTION_LTR) {
value = descEdit.getRight() - descEdit.getCompoundDrawables()[2].getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
showInfoAlert(R.string.media_detail_description,R.string.description_info);
return true;
}
}
else{
value = descEdit.getLeft() + descEdit.getCompoundDrawables()[0].getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
showInfoAlert(R.string.media_detail_description,R.string.description_info);
return true;
}
}
return false;
}
@SuppressLint("StringFormatInvalid")
private void setLicenseSummary(String license) {
String licenseHyperLink = "<a href='" + licenseUrlFor(license)+"'>"+ getString(Utils.licenseNameFor(license)) + "</a><br>";
@ -315,10 +351,12 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
}
public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description);
}
private class TitleTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@ -346,16 +384,9 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
.show();
}
/**
* To launch the Commons:Licensing
* @param view
*/
@OnClick(R.id.licenseInfo)
public void launchLicenseInfo(View view){
Log.i("Language", Locale.getDefault().getLanguage());
UrlLicense urlLicense = new UrlLicense();
urlLicense.initialize();
String url = urlLicense.getLicenseUrl(Locale.getDefault().getLanguage());
Utils.handleWebUrl(getActivity() , Uri.parse(url));
@OnClick(R.id.ll_add_description)
public void onLLAddDescriptionClicked() {
descriptionsAdapter.addDescription(new Description());
rvDescriptions.scrollToPosition(descriptionsAdapter.getItemCount() - 1);
}
}

View file

@ -0,0 +1,92 @@
package fr.free.nrw.commons.upload;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class SpinnerLanguagesAdapter extends ArrayAdapter {
private final int resource;
private final LayoutInflater layoutInflater;
List<Language> languages;
public SpinnerLanguagesAdapter(@NonNull Context context,
int resource) {
super(context, resource);
this.resource = resource;
this.layoutInflater = LayoutInflater.from(context);
languages = new ArrayList<>();
}
public void setLanguages(List<Language> languages) {
this.languages = languages;
}
@Override
public int getCount() {
return languages.size();
}
@Override
public View getDropDownView(int position, @Nullable View convertView,
@NonNull ViewGroup parent) {
View view = layoutInflater.inflate(resource, parent, false);
ViewHolder holder = new ViewHolder(view);
holder.init(position, true);
return view;
}
@Override
public @NonNull
View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view = layoutInflater.inflate(resource, parent, false);
ViewHolder holder = new ViewHolder(view);
holder.init(position, false);
return view;
}
public class ViewHolder {
@BindView(R.id.ll_container_description_language)
LinearLayout llContainerDescriptionLanguage;
@BindView(R.id.tv_language)
TextView tvLanguage;
@BindView(R.id.view)
View view;
public ViewHolder(View itemView) {
ButterKnife.bind(this, itemView);
}
public void init(int position, boolean isDropDownView) {
Language language = languages.get(position);
if (!isDropDownView) {
view.setVisibility(View.GONE);
tvLanguage.setText(
language.getLocale().getLanguage());
} else {
view.setVisibility(View.VISIBLE);
tvLanguage.setText(
String.format("%s [%s]", language.getLocale().getDisplayName(),
language.getLocale().getLanguage()));
}
}
}
}

View file

@ -12,6 +12,7 @@
android:paddingRight="@dimen/standard_gap"
android:paddingStart="@dimen/standard_gap"
android:paddingTop="@dimen/small_gap"
android:nestedScrollingEnabled="false"
android:theme="@style/DarkAppTheme">
<LinearLayout
@ -38,40 +39,36 @@
android:scrollHorizontally="false" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/descEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/mapbox_info_icon_default"
android:drawableRight="@drawable/mapbox_info_icon_default"
android:hint="@string/share_description_hint"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine" />
</android.support.design.widget.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_descriptions"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_marginTop="4dp"
android:orientation="horizontal"
android:id="@+id/ll_add_description"
android:layout_width="wrap_content"
android:layout_gravity="right"
android:gravity="right"
android:padding="10dp"
android:layout_height="wrap_content"
>
<TextView
style="@style/TextAppearance.AppCompat.Body1"
android:text="@string/add_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
<Spinner
android:id="@+id/licenseSpinner"
android:layout_width="0dp"
android:layout_weight="15"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/spinnerTheme" />
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:id="@+id/licenseInfo"
android:layout_height="wrap_content"
android:text="(?)"
android:textColor="@color/primaryTextColor"/>
</LinearLayout>
<Button
android:id="@+id/titleDescButton"

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="10">
<android.support.v7.widget.AppCompatSpinner
android:id="@+id/spinner_description_languages"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:spinnerMode="dialog"></android.support.v7.widget.AppCompatSpinner>
<android.support.design.widget.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7">
<EditText
android:id="@+id/et_description_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/mapbox_info_icon_default"
android:drawableRight="@drawable/mapbox_info_icon_default"
android:hint="@string/share_description_hint"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:id="@+id/ll_container_description_language"
android:orientation="vertical">
<TextView
android:id="@+id/tv_language"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp"
android:imeOptions="flagNoExtractUi"
android:maxLines="1"
style="@style/Base.TextAppearance.AppCompat.Widget.TextView.SpinnerItem"
android:singleLine="true"
tools:text="en"
/>
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/item_white_background"></View>
</LinearLayout>

View file

@ -254,7 +254,7 @@
<string name="get_directions">GET DIRECTIONS</string>
<string name="read_article">READ ARTICLE</string>
<string name="notifications_welcome" formatted="false">Welcome to Wikimedia Commons, %1$s! We\'re glad you\'re here.</string>
<string formatted="false" name="notifications_welcome">Welcome to Wikimedia Commons, %1$s! We\'re glad you\'re here.</string>
<string name="notifications_talk_page_message">%1$s left a message on your talk page</string>
<string name="notifications_thank_you_edit">Thank you for making an edit</string>
<string name="notifications_mention">%1$s mentioned you on %2$s.</string>
@ -287,9 +287,6 @@
<string name="image_uploaded_by">Uploaded by: %1$s</string>
<string name="block_notification">You are blocked from editing Commons</string>
<string name="share_app_title">Share App</string>
<string name="share_coordinates_not_present">Coordinates were not specified during image selection</string>
<string name="error_fetching_nearby_places">Error fetching nearby places.</string>
<string name="appwidget_img">Pic of the Day</string>
<string name="app_widget_heading">Pic of the Day</string>
<string name="menu_search_button">Search</string>
@ -325,6 +322,10 @@
<string name="correct">Correct Answer</string>
<string name="wrong">Wrong Answer</string>
<string name="quiz_screenshot_question">Is this screenshot OK to upload?</string>
<string name="share_app_title">Share App</string>
<string name="share_coordinates_not_present">Coordinates were not specified during image selection</string>
<string name="error_fetching_nearby_places">Error fetching nearby places.</string>
<string name="add_description">+ Add description</string>
<string name="delete_recent_searches_dialog">Are you sure you want to clear your search history?</string>
<string name="search_history_deleted">Search history deleted</string>