From b7090d90c4809a83b9311d2f043ca4a993a232d3 Mon Sep 17 00:00:00 2001 From: Kanahia <114223204+kanahia1@users.noreply.github.com> Date: Mon, 15 Jan 2024 10:52:25 +0530 Subject: [PATCH] Added voice input for caption and description (#5415) * Fixed Grey empty screen at Upload wizard caption step after denying files permission * Empty commit * Fixed loop issue * Created docs for earlier commits * Fixed javadoc * Fixed spaces * Added added basic features to OSM Maps * Added search location feature * Added filter to Open Street Maps * Fixed chipGroup in Open Street Maps * Removed mapBox code * Removed mapBox's code * Reformat code * Reformatted code * Removed rotation feature to map * Removed rotation files and Fixed Marker click problem * Ignored failing tests * Added voice input feature * Fixed test cases * Changed caption and description text --- .../description/DescriptionEditActivity.kt | 18 +- .../upload/UploadMediaDetailAdapter.java | 167 +++++++++++++----- .../UploadMediaDetailFragment.java | 20 ++- .../res/drawable/baseline_keyboard_voice.xml | 10 ++ .../clicked_linearlayout_background.xml | 7 + app/src/main/res/drawable/ic_open.xml | 10 ++ .../main/res/layout/row_item_description.xml | 162 +++++++++++------ app/src/main/res/values/strings.xml | 2 + .../UploadMediaDetailAdapterUnitTest.kt | 8 +- 9 files changed, 296 insertions(+), 108 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_keyboard_voice.xml create mode 100644 app/src/main/res/drawable/clicked_linearlayout_background.xml create mode 100644 app/src/main/res/drawable/ic_open.xml diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt index c7750457e..d2231e416 100644 --- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt @@ -1,9 +1,11 @@ package fr.free.nrw.commons.description +import android.app.Activity.RESULT_OK import android.app.ProgressDialog import android.content.Intent import android.os.Bundle import android.os.Parcelable +import android.speech.RecognizerIntent import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -18,6 +20,7 @@ import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.upload.UploadMediaDetail import fr.free.nrw.commons.upload.UploadMediaDetailAdapter import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog +import timber.log.Timber import javax.inject.Inject /** @@ -55,6 +58,8 @@ class DescriptionEditActivity : BaseActivity(), UploadMediaDetailAdapter.EventLi private lateinit var binding: ActivityDescriptionEditBinding + private val REQUEST_CODE_FOR_VOICE_INPUT = 1213 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -78,7 +83,7 @@ class DescriptionEditActivity : BaseActivity(), UploadMediaDetailAdapter.EventLi * @param descriptionAndCaptions list of description and caption */ private fun initRecyclerView(descriptionAndCaptions: ArrayList?) { - uploadMediaDetailAdapter = UploadMediaDetailAdapter( + uploadMediaDetailAdapter = UploadMediaDetailAdapter(this, savedLanguageValue, descriptionAndCaptions, recentLanguagesDao) uploadMediaDetailAdapter.setCallback { titleStringID: Int, messageStringId: Int -> showInfoAlert( @@ -175,4 +180,15 @@ class DescriptionEditActivity : BaseActivity(), UploadMediaDetailAdapter.EventLi progressDialog!!.setCanceledOnTouchOutside(false) progressDialog!!.show() } + + override + fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_FOR_VOICE_INPUT) { + if (resultCode == RESULT_OK && data != null) { + val result = data.getStringArrayListExtra( RecognizerIntent.EXTRA_RESULTS ) + uploadMediaDetailAdapter.handleSpeechResult(result!![0]) } + else { Timber.e("Error %s", resultCode) } + } + } } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java index 4c5298116..1d2725d95 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java @@ -1,6 +1,9 @@ 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; @@ -13,10 +16,12 @@ import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; @@ -30,11 +35,13 @@ 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 java.util.regex.Pattern; import timber.log.Timber; -public class UploadMediaDetailAdapter extends RecyclerView.Adapter { +public class UploadMediaDetailAdapter extends + RecyclerView.Adapter { RecentLanguagesDao recentLanguagesDao; @@ -47,20 +54,28 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter(); selectedLanguages = new HashMap<>(); this.savedLanguageValue = savedLanguageValue; this.recentLanguagesDao = recentLanguagesDao; + this.fragment = fragment; } - public UploadMediaDetailAdapter(final String savedLanguageValue, + public UploadMediaDetailAdapter(Activity activity, final String savedLanguageValue, List uploadMediaDetails, RecentLanguagesDao recentLanguagesDao) { this.uploadMediaDetails = uploadMediaDetails; selectedLanguages = new HashMap<>(); this.savedLanguageValue = savedLanguageValue; this.recentLanguagesDao = recentLanguagesDao; + this.activity = activity; } public void setCallback(Callback callback) { @@ -77,7 +92,7 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter getItems(){ + public List getItems() { return uploadMediaDetails; } @@ -85,13 +100,14 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter { + 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); - captionInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM); - captionInputLayout.setEndIconDrawable(R.drawable.maplibre_info_icon_default); - captionInputLayout.setEndIconOnClickListener(v -> - callback.showAlert(R.string.media_detail_caption, R.string.caption_info)); - Objects.requireNonNull(captionInputLayout.getEditText()).setFilters(new InputFilter[] { - new UploadMediaDetailInputFilter() - }); - - descInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM); - descInputLayout.setEndIconDrawable(R.drawable.maplibre_info_icon_default); - descInputLayout.setEndIconOnClickListener(v -> - callback.showAlert(R.string.media_detail_description, R.string.description_info)); - + 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); - captionInputLayout.setEndIconDrawable(null); - descInputLayout.setEndIconDrawable(null); + betterCaptionLinearLayout.setVisibility(View.GONE); + betterDescriptionLinearLayout.setVisibility(View.GONE); } removeButton.setOnClickListener(v -> removeDescription(uploadMediaDetail, position)); captionListener = new AbstractTextWatcher( - captionText -> uploadMediaDetails.get(position).setCaptionText(convertIdeographicSpaceToLatinSpace( - removeLeadingAndTrailingWhitespace(captionText)))); + captionText -> uploadMediaDetails.get(position) + .setCaptionText(convertIdeographicSpaceToLatinSpace( + removeLeadingAndTrailingWhitespace(captionText)))); descriptionListener = new AbstractTextWatcher( - descriptionText -> uploadMediaDetails.get(position).setDescriptionText(descriptionText)); + descriptionText -> uploadMediaDetails.get(position) + .setDescriptionText(descriptionText)); captionItemEditText.addTextChangedListener(captionListener); initLanguage(position, uploadMediaDetail); @@ -228,23 +298,26 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter recentLanguages = recentLanguagesDao.getRecentLanguages(); + final List recentLanguages = recentLanguagesDao.getRecentLanguages(); - LanguagesAdapter languagesAdapter = new LanguagesAdapter( + LanguagesAdapter languagesAdapter = new LanguagesAdapter( descriptionLanguages.getContext(), selectedLanguages ); - descriptionLanguages.setOnClickListener(new OnClickListener() { + descriptionLanguages.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Dialog dialog = new Dialog(view.getContext()); dialog.setContentView(R.layout.dialog_select_language); dialog.setCanceledOnTouchOutside(true); - dialog.getWindow().setLayout((int)(view.getContext().getResources().getDisplayMetrics().widthPixels*0.90), - (int)(view.getContext().getResources().getDisplayMetrics().heightPixels*0.90)); + 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); @@ -275,9 +348,10 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter { - onRecentLanguageClicked(dialog, adapterView, position, description); - }); + languageHistoryListView.setOnItemClickListener( + (adapterView, view1, position, id) -> { + onRecentLanguageClicked(dialog, adapterView, position, description); + }); listView.setOnItemClickListener(new OnItemClickListener() { @Override @@ -317,7 +391,7 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter 5) { - for (int i = recentLanguages.size()-1; i >=5; i--) { + for (int i = recentLanguages.size() - 1; i >= 5; i--) { recentLanguagesDao.deleteRecentLanguage(recentLanguages.get(i) .getLanguageCode()); } @@ -425,15 +501,16 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter result = data.getStringArrayListExtra( + RecognizerIntent.EXTRA_RESULTS); + uploadMediaDetailAdapter.handleSpeechResult(result.get(0)); + }else { + Timber.e("Error %s", resultCode); + } + } } /** diff --git a/app/src/main/res/drawable/baseline_keyboard_voice.xml b/app/src/main/res/drawable/baseline_keyboard_voice.xml new file mode 100644 index 000000000..7e567f4cc --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_voice.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/clicked_linearlayout_background.xml b/app/src/main/res/drawable/clicked_linearlayout_background.xml new file mode 100644 index 000000000..0cc738ac4 --- /dev/null +++ b/app/src/main/res/drawable/clicked_linearlayout_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_open.xml b/app/src/main/res/drawable/ic_open.xml new file mode 100644 index 000000000..bb421b50c --- /dev/null +++ b/app/src/main/res/drawable/ic_open.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/row_item_description.xml b/app/src/main/res/layout/row_item_description.xml index 2912c86f4..064448541 100644 --- a/app/src/main/res/layout/row_item_description.xml +++ b/app/src/main/res/layout/row_item_description.xml @@ -7,68 +7,118 @@ android:layout_marginVertical="8dp" app:elevation="6dp"> - + + + + + + + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/description_languages"> - + + - + - + - - + - + - - + - + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 882554084..558508c5b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -784,6 +784,8 @@ Upload your first media by tapping on the add button. Storage Permissions Denied Unable to share this item Permissions are required for functionality + Learn how to write a useful description + Learn how to write a useful caption %d image selected %d images selected diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt index f1651b694..c1547df77 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt @@ -17,12 +17,14 @@ 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 fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment 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.Mockito.mock import org.mockito.MockitoAnnotations import org.powermock.reflect.Whitebox import org.robolectric.Robolectric @@ -55,6 +57,9 @@ class UploadMediaDetailAdapterUnitTest { @Mock private lateinit var textView: TextView + @Mock + private lateinit var fragment : UploadMediaDetailFragment + @Mock private lateinit var view: View @@ -69,7 +74,8 @@ class UploadMediaDetailAdapterUnitTest { MockitoAnnotations.openMocks(this) uploadMediaDetails = mutableListOf(uploadMediaDetail, uploadMediaDetail) activity = Robolectric.buildActivity(UploadActivity::class.java).get() - adapter = UploadMediaDetailAdapter("", recentLanguagesDao) + fragment = mock(UploadMediaDetailFragment::class.java) + adapter = UploadMediaDetailAdapter(fragment,"", recentLanguagesDao) context = ApplicationProvider.getApplicationContext() Whitebox.setInternalState(adapter, "uploadMediaDetails", uploadMediaDetails) Whitebox.setInternalState(adapter, "eventListener", eventListener)