mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 22:03:55 +01:00
Convert UploadMediaDetailAdapter to kotlin
This commit is contained in:
parent
a6c4731f74
commit
87c8224793
8 changed files with 577 additions and 652 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
package fr.free.nrw.commons.description
|
package fr.free.nrw.commons.description
|
||||||
|
|
||||||
import android.app.ProgressDialog
|
import android.app.ProgressDialog
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.speech.RecognizerIntent
|
import android.speech.RecognizerIntent
|
||||||
|
|
@ -72,7 +71,7 @@ class DescriptionEditActivity :
|
||||||
|
|
||||||
private lateinit var binding: ActivityDescriptionEditBinding
|
private lateinit var binding: ActivityDescriptionEditBinding
|
||||||
|
|
||||||
private var descriptionAndCaptions: ArrayList<UploadMediaDetail>? = null
|
private var descriptionAndCaptions: MutableList<UploadMediaDetail>? = null
|
||||||
|
|
||||||
private val voiceInputResultLauncher = registerForActivityResult(
|
private val voiceInputResultLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
|
@ -114,22 +113,18 @@ class DescriptionEditActivity :
|
||||||
* Initializes the RecyclerView
|
* Initializes the RecyclerView
|
||||||
* @param descriptionAndCaptions list of description and caption
|
* @param descriptionAndCaptions list of description and caption
|
||||||
*/
|
*/
|
||||||
private fun initRecyclerView(descriptionAndCaptions: ArrayList<UploadMediaDetail>?) {
|
private fun initRecyclerView(descriptionAndCaptions: MutableList<UploadMediaDetail>?) {
|
||||||
uploadMediaDetailAdapter =
|
uploadMediaDetailAdapter =
|
||||||
UploadMediaDetailAdapter(
|
UploadMediaDetailAdapter(
|
||||||
this,
|
this,
|
||||||
savedLanguageValue,
|
savedLanguageValue,
|
||||||
descriptionAndCaptions,
|
descriptionAndCaptions ?: mutableListOf(),
|
||||||
recentLanguagesDao,
|
recentLanguagesDao,
|
||||||
voiceInputResultLauncher
|
voiceInputResultLauncher
|
||||||
)
|
)
|
||||||
uploadMediaDetailAdapter.setCallback { titleStringID: Int, messageStringId: Int ->
|
|
||||||
showInfoAlert(
|
uploadMediaDetailAdapter.callback = UploadMediaDetailAdapter.Callback(::showInfoAlert)
|
||||||
titleStringID,
|
uploadMediaDetailAdapter.eventListener = this
|
||||||
messageStringId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
uploadMediaDetailAdapter.setEventListener(this)
|
|
||||||
rvDescriptions = binding.rvDescriptionsCaptions
|
rvDescriptions = binding.rvDescriptionsCaptions
|
||||||
rvDescriptions!!.layoutManager = LinearLayoutManager(this)
|
rvDescriptions!!.layoutManager = LinearLayoutManager(this)
|
||||||
rvDescriptions!!.adapter = uploadMediaDetailAdapter
|
rvDescriptions!!.adapter = uploadMediaDetailAdapter
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import java.util.HashMap
|
||||||
class RecentLanguagesAdapter constructor(
|
class RecentLanguagesAdapter constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
var recentLanguages: List<Language>,
|
var recentLanguages: List<Language>,
|
||||||
private val selectedLanguages: HashMap<*, String>,
|
private val selectedLanguages: MutableMap<Int, String>,
|
||||||
) : ArrayAdapter<String?>(context, R.layout.row_item_languages_spinner) {
|
) : ArrayAdapter<String?>(context, R.layout.row_item_languages_spinner) {
|
||||||
/**
|
/**
|
||||||
* Selected language code in UploadMediaDetailAdapter
|
* Selected language code in UploadMediaDetailAdapter
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import java.util.Locale
|
||||||
*/
|
*/
|
||||||
class LanguagesAdapter constructor(
|
class LanguagesAdapter constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val selectedLanguages: HashMap<*, String>,
|
private val selectedLanguages: MutableMap<Int, String>,
|
||||||
) : ArrayAdapter<String?>(context, R.layout.row_item_languages_spinner) {
|
) : ArrayAdapter<String?>(context, R.layout.row_item_languages_spinner) {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,633 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.speech.RecognizerIntent;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.InputFilter;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.databinding.RowItemDescriptionBinding;
|
|
||||||
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.Objects;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
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;
|
|
||||||
private int currentPosition;
|
|
||||||
private Fragment fragment;
|
|
||||||
private Activity activity;
|
|
||||||
private final ActivityResultLauncher<Intent> voiceInputResultLauncher;
|
|
||||||
private SelectedVoiceIcon selectedVoiceIcon;
|
|
||||||
|
|
||||||
private RowItemDescriptionBinding binding;
|
|
||||||
|
|
||||||
public UploadMediaDetailAdapter(Fragment fragment, String savedLanguageValue,
|
|
||||||
RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher<Intent> voiceInputResultLauncher) {
|
|
||||||
uploadMediaDetails = new ArrayList<>();
|
|
||||||
selectedLanguages = new HashMap<>();
|
|
||||||
this.savedLanguageValue = savedLanguageValue;
|
|
||||||
this.recentLanguagesDao = recentLanguagesDao;
|
|
||||||
this.fragment = fragment;
|
|
||||||
this.voiceInputResultLauncher = voiceInputResultLauncher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UploadMediaDetailAdapter(Activity activity, final String savedLanguageValue,
|
|
||||||
List<UploadMediaDetail> uploadMediaDetails, RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher<Intent> voiceInputResultLauncher) {
|
|
||||||
this.uploadMediaDetails = uploadMediaDetails;
|
|
||||||
selectedLanguages = new HashMap<>();
|
|
||||||
this.savedLanguageValue = savedLanguageValue;
|
|
||||||
this.recentLanguagesDao = recentLanguagesDao;
|
|
||||||
this.activity = activity;
|
|
||||||
this.voiceInputResultLauncher = voiceInputResultLauncher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCallback(Callback callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEventListener(EventListener eventListener) {
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItems(List<UploadMediaDetail> uploadMediaDetails) {
|
|
||||||
this.uploadMediaDetails = uploadMediaDetails;
|
|
||||||
selectedLanguages = new HashMap<>();
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<UploadMediaDetail> getItems() {
|
|
||||||
return uploadMediaDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
|
||||||
binding = RowItemDescriptionBinding.inflate(inflater, parent, false);
|
|
||||||
return new ViewHolder(binding.getRoot());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a workaround for a known bug by android here
|
|
||||||
* https://issuetracker.google.com/issues/37095917 makes the edit text on second and subsequent
|
|
||||||
* fragments inside an adapter receptive to long click for copy/paste options
|
|
||||||
*
|
|
||||||
* @param holder the view holder
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onViewAttachedToWindow(@NonNull final ViewHolder holder) {
|
|
||||||
super.onViewAttachedToWindow(holder);
|
|
||||||
holder.captionItemEditText.setEnabled(false);
|
|
||||||
holder.captionItemEditText.setEnabled(true);
|
|
||||||
holder.descItemEditText.setEnabled(false);
|
|
||||||
holder.descItemEditText.setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
|
||||||
holder.bind(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return uploadMediaDetails.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDescription(UploadMediaDetail uploadMediaDetail) {
|
|
||||||
selectedLanguages.put(uploadMediaDetails.size(), "en");
|
|
||||||
this.uploadMediaDetails.add(uploadMediaDetail);
|
|
||||||
notifyItemInserted(uploadMediaDetails.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startSpeechInput(String locale) {
|
|
||||||
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
|
|
||||||
intent.putExtra(
|
|
||||||
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
|
|
||||||
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
|
|
||||||
);
|
|
||||||
intent.putExtra(
|
|
||||||
RecognizerIntent.EXTRA_LANGUAGE,
|
|
||||||
locale
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
voiceInputResultLauncher.launch(intent);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Timber.e(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the result of the speech input by processing the spoken text.
|
|
||||||
* If the spoken text is not empty, it capitalizes the first letter of the spoken text
|
|
||||||
* and updates the appropriate field (caption or description) of the current
|
|
||||||
* UploadMediaDetail based on the selected voice icon.
|
|
||||||
* Finally, it notifies the adapter that the data set has changed.
|
|
||||||
*
|
|
||||||
* @param spokenText the text input received from speech recognition.
|
|
||||||
*/
|
|
||||||
public void handleSpeechResult(String spokenText) {
|
|
||||||
if (!spokenText.isEmpty()) {
|
|
||||||
String spokenTextCapitalized =
|
|
||||||
spokenText.substring(0, 1).toUpperCase() + spokenText.substring(1);
|
|
||||||
if (currentPosition < uploadMediaDetails.size()) {
|
|
||||||
UploadMediaDetail uploadMediaDetail = uploadMediaDetails.get(currentPosition);
|
|
||||||
switch (selectedVoiceIcon) {
|
|
||||||
case CAPTION:
|
|
||||||
uploadMediaDetail.setCaptionText(spokenTextCapitalized);
|
|
||||||
break;
|
|
||||||
case DESCRIPTION:
|
|
||||||
uploadMediaDetail.setDescriptionText(spokenTextCapitalized);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove description based on position from the list and notifies the RecyclerView Adapter that
|
|
||||||
* data in adapter has been removed at that particular position.
|
|
||||||
*
|
|
||||||
* @param uploadMediaDetail
|
|
||||||
* @param position
|
|
||||||
*/
|
|
||||||
public void removeDescription(final UploadMediaDetail uploadMediaDetail, final int position) {
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
this.uploadMediaDetails.remove(uploadMediaDetail);
|
|
||||||
int i = position + 1;
|
|
||||||
while (selectedLanguages.containsKey(i)) {
|
|
||||||
selectedLanguages.remove(i);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
notifyItemRemoved(position);
|
|
||||||
notifyItemRangeChanged(position, uploadMediaDetails.size() - position);
|
|
||||||
updateAddButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
TextView descriptionLanguages ;
|
|
||||||
|
|
||||||
PasteSensitiveTextInputEditText descItemEditText;
|
|
||||||
|
|
||||||
TextInputLayout descInputLayout;
|
|
||||||
|
|
||||||
PasteSensitiveTextInputEditText captionItemEditText;
|
|
||||||
|
|
||||||
TextInputLayout captionInputLayout;
|
|
||||||
|
|
||||||
ImageView removeButton;
|
|
||||||
|
|
||||||
ImageView addButton;
|
|
||||||
|
|
||||||
ConstraintLayout clParent;
|
|
||||||
|
|
||||||
LinearLayout betterCaptionLinearLayout;
|
|
||||||
|
|
||||||
LinearLayout betterDescriptionLinearLayout;
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
AbstractTextWatcher captionListener;
|
|
||||||
|
|
||||||
AbstractTextWatcher descriptionListener;
|
|
||||||
|
|
||||||
public ViewHolder(View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
Timber.i("descItemEditText:" + descItemEditText);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(int position) {
|
|
||||||
UploadMediaDetail uploadMediaDetail = uploadMediaDetails.get(position);
|
|
||||||
Timber.d("UploadMediaDetail is " + uploadMediaDetail);
|
|
||||||
|
|
||||||
descriptionLanguages = binding.descriptionLanguages;
|
|
||||||
descItemEditText = binding.descriptionItemEditText;
|
|
||||||
descInputLayout = binding.descriptionItemEditTextInputLayout;
|
|
||||||
captionItemEditText = binding.captionItemEditText;
|
|
||||||
captionInputLayout = binding.captionItemEditTextInputLayout;
|
|
||||||
removeButton = binding.btnRemove;
|
|
||||||
addButton = binding.btnAdd;
|
|
||||||
clParent = binding.clParent;
|
|
||||||
betterCaptionLinearLayout = binding.llWriteBetterCaption;
|
|
||||||
betterDescriptionLinearLayout = binding.llWriteBetterDescription;
|
|
||||||
|
|
||||||
|
|
||||||
descriptionLanguages.setFocusable(false);
|
|
||||||
captionItemEditText.addTextChangedListener(new AbstractTextWatcher(
|
|
||||||
value -> {
|
|
||||||
if (position == 0) {
|
|
||||||
eventListener.onPrimaryCaptionTextChange(value.length() != 0);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
captionItemEditText.removeTextChangedListener(captionListener);
|
|
||||||
descItemEditText.removeTextChangedListener(descriptionListener);
|
|
||||||
captionItemEditText.setText(uploadMediaDetail.getCaptionText());
|
|
||||||
descItemEditText.setText(uploadMediaDetail.getDescriptionText());
|
|
||||||
captionInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM);
|
|
||||||
captionInputLayout.setEndIconDrawable(R.drawable.baseline_keyboard_voice);
|
|
||||||
captionInputLayout.setEndIconOnClickListener(v -> {
|
|
||||||
currentPosition = position;
|
|
||||||
selectedVoiceIcon = SelectedVoiceIcon.CAPTION;
|
|
||||||
startSpeechInput(descriptionLanguages.getText().toString());
|
|
||||||
});
|
|
||||||
descInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM);
|
|
||||||
descInputLayout.setEndIconDrawable(R.drawable.baseline_keyboard_voice);
|
|
||||||
descInputLayout.setEndIconOnClickListener(v -> {
|
|
||||||
currentPosition = position;
|
|
||||||
selectedVoiceIcon = SelectedVoiceIcon.DESCRIPTION;
|
|
||||||
startSpeechInput(descriptionLanguages.getText().toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (position == 0) {
|
|
||||||
removeButton.setVisibility(View.GONE);
|
|
||||||
betterCaptionLinearLayout.setVisibility(View.VISIBLE);
|
|
||||||
betterCaptionLinearLayout.setOnClickListener(
|
|
||||||
v -> callback.showAlert(R.string.media_detail_caption, R.string.caption_info));
|
|
||||||
betterDescriptionLinearLayout.setVisibility(View.VISIBLE);
|
|
||||||
betterDescriptionLinearLayout.setOnClickListener(
|
|
||||||
v -> callback.showAlert(R.string.media_detail_description,
|
|
||||||
R.string.description_info));
|
|
||||||
Objects.requireNonNull(captionInputLayout.getEditText())
|
|
||||||
.setFilters(new InputFilter[]{
|
|
||||||
new UploadMediaDetailInputFilter()
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
removeButton.setVisibility(View.VISIBLE);
|
|
||||||
betterCaptionLinearLayout.setVisibility(View.GONE);
|
|
||||||
betterDescriptionLinearLayout.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeButton.setOnClickListener(v -> removeDescription(uploadMediaDetail, position));
|
|
||||||
captionListener = new AbstractTextWatcher(
|
|
||||||
captionText -> uploadMediaDetail.setCaptionText(
|
|
||||||
convertIdeographicSpaceToLatinSpace(captionText.strip()))
|
|
||||||
);
|
|
||||||
descriptionListener = new AbstractTextWatcher(
|
|
||||||
descriptionText -> uploadMediaDetail.setDescriptionText(descriptionText));
|
|
||||||
captionItemEditText.addTextChangedListener(captionListener);
|
|
||||||
initLanguage(position, uploadMediaDetail);
|
|
||||||
|
|
||||||
descItemEditText.addTextChangedListener(descriptionListener);
|
|
||||||
initLanguage(position, uploadMediaDetail);
|
|
||||||
|
|
||||||
if (fragment != null) {
|
|
||||||
FrameLayout.LayoutParams newLayoutParams = (FrameLayout.LayoutParams) clParent.getLayoutParams();
|
|
||||||
newLayoutParams.topMargin = 0;
|
|
||||||
newLayoutParams.leftMargin = 0;
|
|
||||||
newLayoutParams.rightMargin = 0;
|
|
||||||
newLayoutParams.bottomMargin = 0;
|
|
||||||
clParent.setLayoutParams(newLayoutParams);
|
|
||||||
}
|
|
||||||
updateAddButtonVisibility();
|
|
||||||
addButton.setOnClickListener(v -> eventListener.addLanguage());
|
|
||||||
|
|
||||||
//If the description was manually added by the user, it deserves focus, if not, let the user decide
|
|
||||||
if (uploadMediaDetail.isManuallyAdded()) {
|
|
||||||
captionItemEditText.requestFocus();
|
|
||||||
} else {
|
|
||||||
captionItemEditText.clearFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void initLanguage(int position, UploadMediaDetail description) {
|
|
||||||
|
|
||||||
final List<Language> recentLanguages = recentLanguagesDao.getRecentLanguages();
|
|
||||||
|
|
||||||
LanguagesAdapter languagesAdapter = new LanguagesAdapter(
|
|
||||||
descriptionLanguages.getContext(),
|
|
||||||
selectedLanguages
|
|
||||||
);
|
|
||||||
|
|
||||||
descriptionLanguages.setOnClickListener(new OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
Dialog dialog = new Dialog(view.getContext());
|
|
||||||
dialog.setContentView(R.layout.dialog_select_language);
|
|
||||||
dialog.setCancelable(false);
|
|
||||||
dialog.getWindow().setLayout(
|
|
||||||
(int) (view.getContext().getResources().getDisplayMetrics().widthPixels
|
|
||||||
* 0.90),
|
|
||||||
(int) (view.getContext().getResources().getDisplayMetrics().heightPixels
|
|
||||||
* 0.90));
|
|
||||||
dialog.show();
|
|
||||||
|
|
||||||
EditText editText = dialog.findViewById(R.id.search_language);
|
|
||||||
ListView listView = dialog.findViewById(R.id.language_list);
|
|
||||||
final Button cancelButton = dialog.findViewById(R.id.cancel_button);
|
|
||||||
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);
|
|
||||||
|
|
||||||
cancelButton.setOnClickListener(v -> dialog.dismiss());
|
|
||||||
|
|
||||||
editText.addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
|
|
||||||
int i2) {
|
|
||||||
hideRecentLanguagesSection();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence charSequence, int i, int i1,
|
|
||||||
int i2) {
|
|
||||||
languagesAdapter.getFilter().filter(charSequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable editable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
long l) {
|
|
||||||
description.setSelectedLanguageIndex(i);
|
|
||||||
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.clear();
|
|
||||||
selectedLanguages.put(position, languageCode);
|
|
||||||
((LanguagesAdapter) adapterView
|
|
||||||
.getAdapter()).setSelectedLangCode(languageCode);
|
|
||||||
Timber.d("Description language code is: " + languageCode);
|
|
||||||
descriptionLanguages.setText(languageCode);
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.setOnDismissListener(
|
|
||||||
dialogInterface -> languagesAdapter.getFilter().filter(""));
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (description.getSelectedLanguageIndex() == -1) {
|
|
||||||
if (!TextUtils.isEmpty(savedLanguageValue)) {
|
|
||||||
// If user has chosen a default language from settings activity
|
|
||||||
// savedLanguageValue is not null
|
|
||||||
if (!TextUtils.isEmpty(description.getLanguageCode())) {
|
|
||||||
descriptionLanguages.setText(description.getLanguageCode());
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
selectedLanguages.put(position, description.getLanguageCode());
|
|
||||||
} else {
|
|
||||||
description.setLanguageCode(savedLanguageValue);
|
|
||||||
descriptionLanguages.setText(savedLanguageValue);
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
selectedLanguages.put(position, savedLanguageValue);
|
|
||||||
}
|
|
||||||
} else if (!TextUtils.isEmpty(description.getLanguageCode())) {
|
|
||||||
descriptionLanguages.setText(description.getLanguageCode());
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
selectedLanguages.put(position, description.getLanguageCode());
|
|
||||||
} else {
|
|
||||||
//Checking whether Language Code attribute is null or not.
|
|
||||||
if (uploadMediaDetails.get(position).getLanguageCode() != null) {
|
|
||||||
//If it is not null that means it is fetching details from the previous
|
|
||||||
// upload (i.e. when user has pressed copy previous caption & description)
|
|
||||||
//hence providing same language code for the current upload.
|
|
||||||
descriptionLanguages.setText(uploadMediaDetails.get(position)
|
|
||||||
.getLanguageCode());
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
selectedLanguages.put(position, uploadMediaDetails.get(position)
|
|
||||||
.getLanguageCode());
|
|
||||||
} else {
|
|
||||||
if (position == 0) {
|
|
||||||
final int defaultLocaleIndex = languagesAdapter
|
|
||||||
.getIndexOfUserDefaultLocale(descriptionLanguages
|
|
||||||
.getContext());
|
|
||||||
descriptionLanguages
|
|
||||||
.setText(languagesAdapter.getLanguageCode(defaultLocaleIndex));
|
|
||||||
description.setLanguageCode(
|
|
||||||
languagesAdapter.getLanguageCode(defaultLocaleIndex));
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
selectedLanguages.put(position,
|
|
||||||
languagesAdapter.getLanguageCode(defaultLocaleIndex));
|
|
||||||
} else {
|
|
||||||
description.setLanguageCode(languagesAdapter.getLanguageCode(0));
|
|
||||||
descriptionLanguages.setText(languagesAdapter.getLanguageCode(0));
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
selectedLanguages.put(position, languagesAdapter.getLanguageCode(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
descriptionLanguages.setText(description.getLanguageCode());
|
|
||||||
selectedLanguages.remove(position);
|
|
||||||
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.clear();
|
|
||||||
selectedLanguages.put(position, languageCode);
|
|
||||||
((RecentLanguagesAdapter) adapterView
|
|
||||||
.getAdapter()).setSelectedLangCode(languageCode);
|
|
||||||
Timber.d("Description language code is: %s", languageCode);
|
|
||||||
if (descriptionLanguages!=null) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (descriptionLanguages!=null) {
|
|
||||||
final RecentLanguagesAdapter recentLanguagesAdapter
|
|
||||||
= new RecentLanguagesAdapter(
|
|
||||||
descriptionLanguages.getContext(),
|
|
||||||
recentLanguagesDao.getRecentLanguages(),
|
|
||||||
selectedLanguages);
|
|
||||||
languageHistoryListView.setAdapter(recentLanguagesAdapter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert Ideographic space to Latin space
|
|
||||||
*
|
|
||||||
* @param source the source text
|
|
||||||
* @return a string with Latin spaces instead of Ideographic spaces
|
|
||||||
*/
|
|
||||||
public String convertIdeographicSpaceToLatinSpace(String source) {
|
|
||||||
Pattern ideographicSpacePattern = Pattern.compile("\\x{3000}");
|
|
||||||
return ideographicSpacePattern.matcher(source).replaceAll(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides the visibility of the "Add" button for all items in the RecyclerView except
|
|
||||||
* the last item in RecyclerView
|
|
||||||
*/
|
|
||||||
private void updateAddButtonVisibility() {
|
|
||||||
int lastItemPosition = getItemCount() - 1;
|
|
||||||
// Hide Add Button for all items
|
|
||||||
for (int i = 0; i < getItemCount(); i++) {
|
|
||||||
if (fragment != null) {
|
|
||||||
if (fragment.getView() != null) {
|
|
||||||
ViewHolder holder = (ViewHolder) ((RecyclerView) fragment.getView()
|
|
||||||
.findViewById(R.id.rv_descriptions)).findViewHolderForAdapterPosition(i);
|
|
||||||
if (holder != null) {
|
|
||||||
holder.addButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.activity != null) {
|
|
||||||
ViewHolder holder = (ViewHolder) ((RecyclerView) activity.findViewById(
|
|
||||||
R.id.rv_descriptions_captions)).findViewHolderForAdapterPosition(i);
|
|
||||||
if (holder != null) {
|
|
||||||
holder.addButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show Add Button for the last item
|
|
||||||
if (fragment != null) {
|
|
||||||
if (fragment.getView() != null) {
|
|
||||||
ViewHolder lastItemHolder = (ViewHolder) ((RecyclerView) fragment.getView()
|
|
||||||
.findViewById(R.id.rv_descriptions)).findViewHolderForAdapterPosition(
|
|
||||||
lastItemPosition);
|
|
||||||
if (lastItemHolder != null) {
|
|
||||||
lastItemHolder.addButton.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.activity != null) {
|
|
||||||
ViewHolder lastItemHolder = (ViewHolder) ((RecyclerView) activity
|
|
||||||
.findViewById(R.id.rv_descriptions_captions)).findViewHolderForAdapterPosition(
|
|
||||||
lastItemPosition);
|
|
||||||
if (lastItemHolder != null) {
|
|
||||||
lastItemHolder.addButton.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback {
|
|
||||||
|
|
||||||
void showAlert(int mediaDetailDescription, int descriptionInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface EventListener {
|
|
||||||
|
|
||||||
void onPrimaryCaptionTextChange(boolean isNotEmpty);
|
|
||||||
|
|
||||||
void addLanguage();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SelectedVoiceIcon {
|
|
||||||
CAPTION,
|
|
||||||
DESCRIPTION
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,563 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH
|
||||||
|
import android.speech.RecognizerIntent.EXTRA_LANGUAGE
|
||||||
|
import android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL
|
||||||
|
import android.speech.RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.InputFilter
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ListView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.databinding.RowItemDescriptionBinding
|
||||||
|
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.utils.AbstractTextWatcher
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class UploadMediaDetailAdapter : RecyclerView.Adapter<UploadMediaDetailAdapter.ViewHolder> {
|
||||||
|
private var uploadMediaDetails: MutableList<UploadMediaDetail>
|
||||||
|
private var selectedLanguages: MutableMap<Int, String>
|
||||||
|
private val savedLanguageValue: String
|
||||||
|
private var recentLanguagesTextView: TextView? = null
|
||||||
|
private var separator: View? = null
|
||||||
|
private var languageHistoryListView: ListView? = null
|
||||||
|
private var currentPosition = 0
|
||||||
|
private var fragment: Fragment? = null
|
||||||
|
private var activity: Activity? = null
|
||||||
|
private val voiceInputResultLauncher: ActivityResultLauncher<Intent>
|
||||||
|
private var selectedVoiceIcon: SelectedVoiceIcon? = null
|
||||||
|
var recentLanguagesDao: RecentLanguagesDao
|
||||||
|
var callback: Callback? = null
|
||||||
|
var eventListener: EventListener? = null
|
||||||
|
var items: List<UploadMediaDetail>
|
||||||
|
get() = uploadMediaDetails
|
||||||
|
set(value) {
|
||||||
|
uploadMediaDetails = value.toMutableList()
|
||||||
|
selectedLanguages = mutableMapOf()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
fragment: Fragment?,
|
||||||
|
savedLanguageValue: String,
|
||||||
|
recentLanguagesDao: RecentLanguagesDao,
|
||||||
|
voiceInputResultLauncher: ActivityResultLauncher<Intent>
|
||||||
|
) {
|
||||||
|
uploadMediaDetails = ArrayList()
|
||||||
|
selectedLanguages = mutableMapOf()
|
||||||
|
this.savedLanguageValue = savedLanguageValue
|
||||||
|
this.recentLanguagesDao = recentLanguagesDao
|
||||||
|
this.fragment = fragment
|
||||||
|
this.voiceInputResultLauncher = voiceInputResultLauncher
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
activity: Activity?,
|
||||||
|
savedLanguageValue: String,
|
||||||
|
uploadMediaDetails: MutableList<UploadMediaDetail>,
|
||||||
|
recentLanguagesDao: RecentLanguagesDao,
|
||||||
|
voiceInputResultLauncher: ActivityResultLauncher<Intent>
|
||||||
|
) {
|
||||||
|
this.uploadMediaDetails = uploadMediaDetails
|
||||||
|
selectedLanguages = HashMap()
|
||||||
|
this.savedLanguageValue = savedLanguageValue
|
||||||
|
this.recentLanguagesDao = recentLanguagesDao
|
||||||
|
this.activity = activity
|
||||||
|
this.voiceInputResultLauncher = voiceInputResultLauncher
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
return ViewHolder(RowItemDescriptionBinding.inflate(inflater, parent, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a workaround for a known bug by android here
|
||||||
|
* https://issuetracker.google.com/issues/37095917 makes the edit text on second and subsequent
|
||||||
|
* fragments inside an adapter receptive to long click for copy/paste options
|
||||||
|
*
|
||||||
|
* @param holder the view holder
|
||||||
|
*/
|
||||||
|
override fun onViewAttachedToWindow(holder: ViewHolder) {
|
||||||
|
super.onViewAttachedToWindow(holder)
|
||||||
|
holder.binding.captionItemEditText.isEnabled = false
|
||||||
|
holder.binding.captionItemEditText.isEnabled = true
|
||||||
|
holder.binding.descriptionItemEditText.isEnabled = false
|
||||||
|
holder.binding.descriptionItemEditText.isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.bind(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return uploadMediaDetails.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDescription(uploadMediaDetail: UploadMediaDetail) {
|
||||||
|
selectedLanguages[uploadMediaDetails.size] = "en"
|
||||||
|
uploadMediaDetails.add(uploadMediaDetail)
|
||||||
|
notifyItemInserted(uploadMediaDetails.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startSpeechInput(locale: String) {
|
||||||
|
try {
|
||||||
|
voiceInputResultLauncher.launch(Intent(ACTION_RECOGNIZE_SPEECH).apply {
|
||||||
|
putExtra(EXTRA_LANGUAGE_MODEL, LANGUAGE_MODEL_FREE_FORM)
|
||||||
|
putExtra(EXTRA_LANGUAGE, locale)
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the result of the speech input by processing the spoken text.
|
||||||
|
* If the spoken text is not empty, it capitalizes the first letter of the spoken text
|
||||||
|
* and updates the appropriate field (caption or description) of the current
|
||||||
|
* UploadMediaDetail based on the selected voice icon.
|
||||||
|
* Finally, it notifies the adapter that the data set has changed.
|
||||||
|
*
|
||||||
|
* @param spokenText the text input received from speech recognition.
|
||||||
|
*/
|
||||||
|
fun handleSpeechResult(spokenText: String) {
|
||||||
|
if (spokenText.isNotEmpty()) {
|
||||||
|
val spokenTextCapitalized =
|
||||||
|
spokenText.substring(0, 1).uppercase(Locale.getDefault()) + spokenText.substring(1)
|
||||||
|
if (currentPosition < uploadMediaDetails.size) {
|
||||||
|
val uploadMediaDetail = uploadMediaDetails[currentPosition]
|
||||||
|
when (selectedVoiceIcon) {
|
||||||
|
SelectedVoiceIcon.CAPTION -> uploadMediaDetail.captionText =
|
||||||
|
spokenTextCapitalized
|
||||||
|
|
||||||
|
SelectedVoiceIcon.DESCRIPTION -> uploadMediaDetail.descriptionText =
|
||||||
|
spokenTextCapitalized
|
||||||
|
|
||||||
|
null -> {}
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove description based on position from the list and notifies the RecyclerView Adapter that
|
||||||
|
* data in adapter has been removed at that particular position.
|
||||||
|
*/
|
||||||
|
fun removeDescription(uploadMediaDetail: UploadMediaDetail, position: Int) {
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
uploadMediaDetails.remove(uploadMediaDetail)
|
||||||
|
var i = position + 1
|
||||||
|
while (selectedLanguages.containsKey(i)) {
|
||||||
|
selectedLanguages.remove(i)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemRangeChanged(position, uploadMediaDetails.size - position)
|
||||||
|
updateAddButtonVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(val binding: RowItemDescriptionBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
var addButton: ImageView? = null
|
||||||
|
|
||||||
|
var clParent: ConstraintLayout? = null
|
||||||
|
|
||||||
|
var betterCaptionLinearLayout: LinearLayout? = null
|
||||||
|
|
||||||
|
var betterDescriptionLinearLayout: LinearLayout? = null
|
||||||
|
|
||||||
|
private var captionListener: AbstractTextWatcher? = null
|
||||||
|
|
||||||
|
var descriptionListener: AbstractTextWatcher? = null
|
||||||
|
|
||||||
|
fun bind(position: Int) {
|
||||||
|
val uploadMediaDetail = uploadMediaDetails[position]
|
||||||
|
Timber.d("UploadMediaDetail is %s", uploadMediaDetail)
|
||||||
|
|
||||||
|
addButton = binding.btnAdd
|
||||||
|
clParent = binding.clParent
|
||||||
|
betterCaptionLinearLayout = binding.llWriteBetterCaption
|
||||||
|
betterDescriptionLinearLayout = binding.llWriteBetterDescription
|
||||||
|
|
||||||
|
|
||||||
|
binding.descriptionLanguages.isFocusable = false
|
||||||
|
binding.captionItemEditText.addTextChangedListener(AbstractTextWatcher { value: String ->
|
||||||
|
if (position == 0) {
|
||||||
|
eventListener!!.onPrimaryCaptionTextChange(value.length != 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.captionItemEditText.removeTextChangedListener(captionListener)
|
||||||
|
binding.descriptionItemEditText.removeTextChangedListener(descriptionListener)
|
||||||
|
binding.captionItemEditText.setText(uploadMediaDetail.captionText)
|
||||||
|
binding.descriptionItemEditText.setText(uploadMediaDetail.descriptionText)
|
||||||
|
binding.captionItemEditTextInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
|
||||||
|
binding.captionItemEditTextInputLayout.setEndIconDrawable(R.drawable.baseline_keyboard_voice)
|
||||||
|
binding.captionItemEditTextInputLayout.setEndIconOnClickListener { v: View? ->
|
||||||
|
currentPosition = position
|
||||||
|
selectedVoiceIcon = SelectedVoiceIcon.CAPTION
|
||||||
|
startSpeechInput(binding.descriptionLanguages.text.toString())
|
||||||
|
}
|
||||||
|
binding.descriptionItemEditTextInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
|
||||||
|
binding.descriptionItemEditTextInputLayout.setEndIconDrawable(R.drawable.baseline_keyboard_voice)
|
||||||
|
binding.descriptionItemEditTextInputLayout.setEndIconOnClickListener { v: View? ->
|
||||||
|
currentPosition = position
|
||||||
|
selectedVoiceIcon = SelectedVoiceIcon.DESCRIPTION
|
||||||
|
startSpeechInput(binding.descriptionLanguages.text.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position == 0) {
|
||||||
|
binding.btnRemove.visibility = View.GONE
|
||||||
|
betterCaptionLinearLayout!!.visibility = View.VISIBLE
|
||||||
|
betterCaptionLinearLayout!!.setOnClickListener { v: View? ->
|
||||||
|
callback!!.showAlert(
|
||||||
|
R.string.media_detail_caption,
|
||||||
|
R.string.caption_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
betterDescriptionLinearLayout!!.visibility = View.VISIBLE
|
||||||
|
betterDescriptionLinearLayout!!.setOnClickListener { v: View? ->
|
||||||
|
callback!!.showAlert(
|
||||||
|
R.string.media_detail_description,
|
||||||
|
R.string.description_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.captionItemEditTextInputLayout.editText?.let {
|
||||||
|
it.filters = arrayOf<InputFilter>(UploadMediaDetailInputFilter())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.btnRemove.visibility = View.VISIBLE
|
||||||
|
betterCaptionLinearLayout!!.visibility = View.GONE
|
||||||
|
betterDescriptionLinearLayout!!.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnRemove.setOnClickListener { v: View? ->
|
||||||
|
removeDescription(
|
||||||
|
uploadMediaDetail,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
}
|
||||||
|
captionListener = AbstractTextWatcher { captionText: String ->
|
||||||
|
uploadMediaDetail.captionText =
|
||||||
|
convertIdeographicSpaceToLatinSpace(captionText.trim())
|
||||||
|
}
|
||||||
|
descriptionListener = AbstractTextWatcher { value: String? ->
|
||||||
|
uploadMediaDetail.descriptionText = value
|
||||||
|
}
|
||||||
|
binding.captionItemEditText.addTextChangedListener(captionListener)
|
||||||
|
initLanguage(position, uploadMediaDetail)
|
||||||
|
|
||||||
|
binding.descriptionItemEditText.addTextChangedListener(descriptionListener)
|
||||||
|
initLanguage(position, uploadMediaDetail)
|
||||||
|
|
||||||
|
if (fragment != null) {
|
||||||
|
val newLayoutParams = clParent!!.layoutParams as FrameLayout.LayoutParams
|
||||||
|
newLayoutParams.topMargin = 0
|
||||||
|
newLayoutParams.leftMargin = 0
|
||||||
|
newLayoutParams.rightMargin = 0
|
||||||
|
newLayoutParams.bottomMargin = 0
|
||||||
|
clParent!!.layoutParams = newLayoutParams
|
||||||
|
}
|
||||||
|
updateAddButtonVisibility()
|
||||||
|
addButton!!.setOnClickListener { v: View? -> eventListener!!.addLanguage() }
|
||||||
|
|
||||||
|
//If the description was manually added by the user, it deserves focus, if not, let the user decide
|
||||||
|
if (uploadMediaDetail.isManuallyAdded) {
|
||||||
|
binding.captionItemEditText.requestFocus()
|
||||||
|
} else {
|
||||||
|
binding.captionItemEditText.clearFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun initLanguage(position: Int, description: UploadMediaDetail) {
|
||||||
|
val recentLanguages = recentLanguagesDao.getRecentLanguages()
|
||||||
|
|
||||||
|
val languagesAdapter = LanguagesAdapter(
|
||||||
|
binding.descriptionLanguages.context,
|
||||||
|
selectedLanguages
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.descriptionLanguages.setOnClickListener { view ->
|
||||||
|
val dialog = Dialog(view.context)
|
||||||
|
dialog.setContentView(R.layout.dialog_select_language)
|
||||||
|
dialog.setCancelable(false)
|
||||||
|
dialog.window!!.setLayout(
|
||||||
|
(view.context.resources.displayMetrics.widthPixels
|
||||||
|
* 0.90).toInt(),
|
||||||
|
(view.context.resources.displayMetrics.heightPixels
|
||||||
|
* 0.90).toInt()
|
||||||
|
)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
val editText =
|
||||||
|
dialog.findViewById<EditText>(R.id.search_language)
|
||||||
|
val listView =
|
||||||
|
dialog.findViewById<ListView>(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.adapter = languagesAdapter
|
||||||
|
|
||||||
|
editText.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) =
|
||||||
|
hideRecentLanguagesSection()
|
||||||
|
|
||||||
|
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
||||||
|
languagesAdapter.filter.filter(charSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterTextChanged(editable: Editable) = Unit
|
||||||
|
})
|
||||||
|
|
||||||
|
languageHistoryListView?.setOnItemClickListener { adapterView: AdapterView<*>, view1: View?, position: Int, id: Long ->
|
||||||
|
onRecentLanguageClicked(dialog, adapterView, position, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
listView.onItemClickListener = OnItemClickListener { adapterView, _, i, l ->
|
||||||
|
description.selectedLanguageIndex = i
|
||||||
|
val languageCode = (adapterView.adapter as LanguagesAdapter).getLanguageCode(i)
|
||||||
|
description.languageCode = languageCode
|
||||||
|
val languageName = (adapterView.adapter as LanguagesAdapter).getLanguageName(i)
|
||||||
|
val isExists = recentLanguagesDao.findRecentLanguage(languageCode)
|
||||||
|
if (isExists) {
|
||||||
|
recentLanguagesDao.deleteRecentLanguage(languageCode)
|
||||||
|
}
|
||||||
|
recentLanguagesDao.addRecentLanguage(Language(languageName, languageCode))
|
||||||
|
|
||||||
|
selectedLanguages.clear()
|
||||||
|
selectedLanguages[position] = languageCode
|
||||||
|
(adapterView.adapter as LanguagesAdapter).selectedLangCode = languageCode
|
||||||
|
Timber.d("Description language code is: %s", languageCode)
|
||||||
|
binding.descriptionLanguages.text = languageCode
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.setOnDismissListener {
|
||||||
|
languagesAdapter.filter.filter("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description.selectedLanguageIndex == -1) {
|
||||||
|
if (!TextUtils.isEmpty(savedLanguageValue)) {
|
||||||
|
// If user has chosen a default language from settings activity
|
||||||
|
// savedLanguageValue is not null
|
||||||
|
if (!TextUtils.isEmpty(description.languageCode)) {
|
||||||
|
binding.descriptionLanguages.text = description.languageCode
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
selectedLanguages[position] = description.languageCode!!
|
||||||
|
} else {
|
||||||
|
description.languageCode = savedLanguageValue
|
||||||
|
binding.descriptionLanguages.text = savedLanguageValue
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
selectedLanguages[position] = savedLanguageValue
|
||||||
|
}
|
||||||
|
} else if (!TextUtils.isEmpty(description.languageCode)) {
|
||||||
|
binding.descriptionLanguages.text = description.languageCode
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
selectedLanguages[position] = description.languageCode!!
|
||||||
|
} else {
|
||||||
|
//Checking whether Language Code attribute is null or not.
|
||||||
|
if (uploadMediaDetails[position].languageCode != null) {
|
||||||
|
//If it is not null that means it is fetching details from the previous
|
||||||
|
// upload (i.e. when user has pressed copy previous caption & description)
|
||||||
|
//hence providing same language code for the current upload.
|
||||||
|
binding.descriptionLanguages.text = uploadMediaDetails[position]
|
||||||
|
.languageCode
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
selectedLanguages[position] = uploadMediaDetails[position].languageCode!!
|
||||||
|
} else {
|
||||||
|
if (position == 0) {
|
||||||
|
val defaultLocaleIndex = languagesAdapter.getIndexOfUserDefaultLocale(
|
||||||
|
binding.descriptionLanguages.getContext())
|
||||||
|
binding.descriptionLanguages.setText(languagesAdapter.getLanguageCode(defaultLocaleIndex))
|
||||||
|
description.languageCode = languagesAdapter.getLanguageCode(defaultLocaleIndex)
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
selectedLanguages[position] =
|
||||||
|
languagesAdapter.getLanguageCode(defaultLocaleIndex)
|
||||||
|
} else {
|
||||||
|
description.languageCode = languagesAdapter.getLanguageCode(0)
|
||||||
|
binding.descriptionLanguages.text = languagesAdapter.getLanguageCode(0)
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
selectedLanguages[position] = languagesAdapter.getLanguageCode(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.descriptionLanguages.text = description.languageCode
|
||||||
|
selectedLanguages.remove(position)
|
||||||
|
description.languageCode?.let {
|
||||||
|
selectedLanguages[position] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles click event for recent language section
|
||||||
|
*/
|
||||||
|
private fun onRecentLanguageClicked(
|
||||||
|
dialog: Dialog, adapterView: AdapterView<*>,
|
||||||
|
position: Int, description: UploadMediaDetail
|
||||||
|
) {
|
||||||
|
description.selectedLanguageIndex = position
|
||||||
|
val languageCode = (adapterView.adapter as RecentLanguagesAdapter)
|
||||||
|
.getLanguageCode(position)
|
||||||
|
description.languageCode = languageCode
|
||||||
|
val languageName = (adapterView.adapter as RecentLanguagesAdapter)
|
||||||
|
.getLanguageName(position)
|
||||||
|
val isExists = recentLanguagesDao.findRecentLanguage(languageCode)
|
||||||
|
if (isExists) {
|
||||||
|
recentLanguagesDao.deleteRecentLanguage(languageCode)
|
||||||
|
}
|
||||||
|
recentLanguagesDao.addRecentLanguage(Language(languageName, languageCode))
|
||||||
|
|
||||||
|
selectedLanguages.clear()
|
||||||
|
selectedLanguages[position] = languageCode
|
||||||
|
(adapterView
|
||||||
|
.adapter as RecentLanguagesAdapter).selectedLangCode = languageCode
|
||||||
|
Timber.d("Description language code is: %s", languageCode)
|
||||||
|
binding.descriptionLanguages.text = languageCode
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides recent languages section
|
||||||
|
*/
|
||||||
|
private fun hideRecentLanguagesSection() {
|
||||||
|
languageHistoryListView!!.visibility = View.GONE
|
||||||
|
recentLanguagesTextView!!.visibility = View.GONE
|
||||||
|
separator!!.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up recent languages section
|
||||||
|
*
|
||||||
|
* @param recentLanguages recently used languages
|
||||||
|
*/
|
||||||
|
private fun setUpRecentLanguagesSection(recentLanguages: List<Language>) {
|
||||||
|
if (recentLanguages.isEmpty()) {
|
||||||
|
languageHistoryListView!!.visibility = View.GONE
|
||||||
|
recentLanguagesTextView!!.visibility = View.GONE
|
||||||
|
separator!!.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
if (recentLanguages.size > 5) {
|
||||||
|
for (i in recentLanguages.size - 1 downTo 5) {
|
||||||
|
recentLanguagesDao.deleteRecentLanguage(
|
||||||
|
recentLanguages[i]
|
||||||
|
.languageCode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
languageHistoryListView!!.visibility = View.VISIBLE
|
||||||
|
recentLanguagesTextView!!.visibility = View.VISIBLE
|
||||||
|
separator!!.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
val recentLanguagesAdapter = RecentLanguagesAdapter(
|
||||||
|
binding.descriptionLanguages.context,
|
||||||
|
recentLanguagesDao.getRecentLanguages(),
|
||||||
|
selectedLanguages
|
||||||
|
)
|
||||||
|
languageHistoryListView!!.adapter = recentLanguagesAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Ideographic space to Latin space
|
||||||
|
*
|
||||||
|
* @param source the source text
|
||||||
|
* @return a string with Latin spaces instead of Ideographic spaces
|
||||||
|
*/
|
||||||
|
fun convertIdeographicSpaceToLatinSpace(source: String): String {
|
||||||
|
val ideographicSpacePattern = Pattern.compile("\\x{3000}")
|
||||||
|
return ideographicSpacePattern.matcher(source).replaceAll(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the visibility of the "Add" button for all items in the RecyclerView except
|
||||||
|
* the last item in RecyclerView
|
||||||
|
*/
|
||||||
|
private fun updateAddButtonVisibility() {
|
||||||
|
val lastItemPosition = itemCount - 1
|
||||||
|
// Hide Add Button for all items
|
||||||
|
for (i in 0 until itemCount) {
|
||||||
|
if (fragment != null) {
|
||||||
|
if (fragment!!.view != null) {
|
||||||
|
val holder = (fragment!!.requireView().findViewById<View>(R.id.rv_descriptions) as RecyclerView).findViewHolderForAdapterPosition(i) as ViewHolder?
|
||||||
|
if (holder != null) {
|
||||||
|
holder.addButton!!.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (activity != null) {
|
||||||
|
val holder = (activity!!.findViewById<View>(R.id.rv_descriptions_captions) as RecyclerView).findViewHolderForAdapterPosition(i) as ViewHolder?
|
||||||
|
if (holder != null) {
|
||||||
|
holder.addButton!!.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Add Button for the last item
|
||||||
|
if (fragment != null) {
|
||||||
|
if (fragment!!.view != null) {
|
||||||
|
val lastItemHolder = (fragment!!.requireView().findViewById<View>(R.id.rv_descriptions) as RecyclerView).findViewHolderForAdapterPosition(lastItemPosition) as ViewHolder?
|
||||||
|
if (lastItemHolder != null) {
|
||||||
|
lastItemHolder.addButton!!.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (activity != null) {
|
||||||
|
val lastItemHolder = (activity!!.findViewById<View>(R.id.rv_descriptions_captions) as RecyclerView).findViewHolderForAdapterPosition(lastItemPosition) as ViewHolder?
|
||||||
|
if (lastItemHolder != null) {
|
||||||
|
lastItemHolder.addButton!!.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface Callback {
|
||||||
|
fun showAlert(mediaDetailDescription: Int, descriptionInfo: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventListener {
|
||||||
|
fun onPrimaryCaptionTextChange(isNotEmpty: Boolean)
|
||||||
|
fun addLanguage()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum class SelectedVoiceIcon {
|
||||||
|
CAPTION,
|
||||||
|
DESCRIPTION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -223,7 +223,7 @@ class SettingsFragmentUnitTests {
|
||||||
RecentLanguagesAdapter(
|
RecentLanguagesAdapter(
|
||||||
context,
|
context,
|
||||||
listOf(Language("English", "en")),
|
listOf(Language("English", "en")),
|
||||||
hashMapOf<String, String>(),
|
mutableMapOf(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val method: Method =
|
val method: Method =
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class LanguagesAdapterTest {
|
||||||
private lateinit var context: Context
|
private lateinit var context: Context
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var selectedLanguages: HashMap<Integer, String>
|
private lateinit var selectedLanguages: MutableMap<Int, String>
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var parent: ViewGroup
|
private lateinit var parent: ViewGroup
|
||||||
|
|
@ -41,7 +41,7 @@ class LanguagesAdapterTest {
|
||||||
|
|
||||||
private lateinit var languagesAdapter: LanguagesAdapter
|
private lateinit var languagesAdapter: LanguagesAdapter
|
||||||
private lateinit var convertView: View
|
private lateinit var convertView: View
|
||||||
private var selectLanguages: HashMap<Integer, String> = HashMap()
|
private var selectLanguages: MutableMap<Int, String> = mutableMapOf()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
|
|
@ -94,8 +94,8 @@ class LanguagesAdapterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSelectLanguageNotEmpty() {
|
fun testSelectLanguageNotEmpty() {
|
||||||
selectLanguages[Integer(0)] = "es"
|
selectLanguages[0] = "es"
|
||||||
selectLanguages[Integer(1)] = "de"
|
selectLanguages[1] = "de"
|
||||||
languagesAdapter = LanguagesAdapter(context, selectLanguages)
|
languagesAdapter = LanguagesAdapter(context, selectLanguages)
|
||||||
|
|
||||||
Assertions.assertEquals(false, languagesAdapter.isEnabled(languagesAdapter.getIndexOfLanguageCode("es")))
|
Assertions.assertEquals(false, languagesAdapter.isEnabled(languagesAdapter.getIndexOfLanguageCode("es")))
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ class UploadMediaDetailAdapterUnitTest {
|
||||||
RecentLanguagesAdapter(
|
RecentLanguagesAdapter(
|
||||||
context,
|
context,
|
||||||
listOf(Language("English", "en")),
|
listOf(Language("English", "en")),
|
||||||
hashMapOf<String, String>(),
|
mutableMapOf(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val method: Method =
|
val method: Method =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue