Skip to content

Commit

Permalink
Merge pull request #212 from caarmen/rewrite-search-ui
Browse files Browse the repository at this point in the history
Update the theme to material 3, with new search ui, in addition to other smaller changes).
  • Loading branch information
caarmen authored Jan 4, 2025
2 parents 55d8da5 + 71846cf commit b58e175
Show file tree
Hide file tree
Showing 81 changed files with 483 additions and 161 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ android {
namespace "ca.rmen.android.poetassistant"
minSdkVersion 21
targetSdkVersion 35
versionCode 113010
versionName "1.30.10"
versionCode 113100
versionName "1.31.0"
// setting vectorDrawables.useSupportLibrary = true means pngs won't be generated at
// build time: http://android-developers.blogspot.fr/2016/02/android-support-library-232.html
vectorDrawables.useSupportLibrary = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ private void runIntegrationTest(IntegrationTestScenario data) {
checkAllStarredWords(context, data.secondSynonymForFirstRhyme);
swipeViewPagerRight(3);
checkStarredInList(data.secondSynonymForFirstRhyme);
addFilter(data.thesaurusFilter, data.thesaurusFilterMatch);
clearFilter(data.firstSynonymForFirstRhyme);
addFilter(Tab.THESAURUS, data.thesaurusFilter, data.thesaurusFilterMatch);
clearFilter(Tab.THESAURUS, data.firstSynonymForFirstRhyme);
swipeViewPagerRight(1);
addFilter(data.rhymerFilter, data.rhymerFilterMatch);
clearFilter(data.firstRhyme);
addFilter(Tab.RHYMER, data.rhymerFilter, data.rhymerFilterMatch);
clearFilter(Tab.RHYMER, data.firstRhyme);
swipeViewPagerLeft(3);
typeAndSpeakPoem(data.poem);
clearPoem();
Expand All @@ -161,11 +161,11 @@ private void runCleanLayoutIntegrationTest(IntegrationTestScenario data) {
checkAllStarredWords(context, data.secondSynonymForFirstRhyme);
swipeViewPagerRight(3);
checkStarredInList(data.secondSynonymForFirstRhyme);
addFilter(data.thesaurusFilter, data.thesaurusFilterMatch);
clearFilter(data.firstSynonymForFirstRhyme);
addFilter(Tab.THESAURUS, data.thesaurusFilter, data.thesaurusFilterMatch);
clearFilter(Tab.THESAURUS, data.firstSynonymForFirstRhyme);
swipeViewPagerRight(1);
addFilter(data.rhymerFilter, data.rhymerFilterMatch);
clearFilter(data.firstRhyme);
addFilter(Tab.RHYMER, data.rhymerFilter, data.rhymerFilterMatch);
clearFilter(Tab.RHYMER, data.firstRhyme);
swipeViewPagerLeft(3);
typeAndSpeakPoem(data.poem);
clearPoem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,10 @@ public void testLookupSetting() {
swipeViewPagerLeft(3);
String poemText = "Here is a poem with lookup disabled";
typePoem(poemText);
pressBack();

assertPopupMissing("rhymer");
assertPopupMissing("thesaurus");
assertPopupMissing("dictionary");
pressBack();
closeSoftKeyboard();

openMenuItem(R.string.action_settings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class RandomWordTest {
@Test
public void openWotdListTest() {
openMenuItem(R.string.action_wotd_history);
Matcher<View> latestEntryViewMatcher = childAtPosition(withId(R.id.recycler_view), 0);
Matcher<View> latestEntryViewMatcher = childAtPosition(withId(R.id.wotd_recycler_view), 0);
// Check that the date field in the first (most recent) entry in the Wotd list contains today's date.
Calendar cal = Calendar.getInstance();
int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
Expand All @@ -90,7 +90,7 @@ public void openWotdListTest() {
onView(allOf(withId(R.id.btn_star_result), isDescendantOfA(latestEntryViewMatcher)))
.perform(click());
swipeViewPagerRight(1);
onView(allOf(withId(R.id.recycler_view), isDisplayed())).check(matches(withAdapterItemCount(1)));
onView(allOf(withId(R.id.favorites_recycler_view), isDisplayed())).check(matches(withAdapterItemCount(1)));

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@


import android.content.Context;

import androidx.test.espresso.Espresso;
import androidx.test.espresso.ViewInteraction;
import androidx.test.filters.LargeTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
Expand Down Expand Up @@ -104,9 +106,14 @@ public void searchHistoryTest() {
searchAutoComplete.perform(typeText("o"));
checkSearchSuggestions("awesome", "awesomely");

Espresso.pressBack();
Espresso.onIdle();
searchAutoComplete.perform(clearText());
searchAutoComplete.perform(typeText("carme"));
checkSearchSuggestions("carmen", "carmelite");
Espresso.pressBack();
Espresso.pressBack();
Espresso.onIdle();

clearSearchHistory();

Expand Down Expand Up @@ -144,7 +151,7 @@ public void patternSearchWithFavoriteTest() {
@Test
public void patternSearchTooManyResultsTest() {
search("a*");
onView(allOf(withId(R.id.recycler_view), isDisplayed()))
onView(allOf(withId(R.id.pattern_recycler_view), isDisplayed()))
.check(matches(withAdapterItemCount(501)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void shareFilteredThesaurusTest() {
Context context = mActivityTestRule.getActivity();
search("happy");
swipeViewPagerLeft(1);
addFilter("messed", "blessed");
addFilter(Tab.THESAURUS, "messed", "blessed");
openMenuItem(R.string.share);
String expectedContent = context.getString(R.string.share_thesaurus_title_with_filter, "happy", "messed");
checkShareIntentContains(expectedContent);
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/AppTheme">
<activity
android:name=".main.MainActivity"
Expand Down Expand Up @@ -158,7 +159,6 @@
android:value="ca.rmen.android.poetassistant.about.AboutActivity" />
</activity>
<activity
android:theme="@style/AppTheme.Settings"
android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".main.MainActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,18 @@ package ca.rmen.android.poetassistant.databinding
import androidx.databinding.BindingAdapter
import androidx.annotation.DrawableRes
import android.widget.ImageView
import com.google.android.material.button.MaterialButton

object DataBindingAdapters {
@JvmStatic
@BindingAdapter("srcCompat")
fun setImageResource(imageView: ImageView, @DrawableRes resource: Int) {
imageView.setImageResource(resource)
}

@JvmStatic
@BindingAdapter("iconCompat")
fun setIcon(button: MaterialButton, @DrawableRes resource: Int) {
button.setIconResource(resource)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.viewpager.widget.ViewPager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.Window
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.activity.OnBackPressedCallback
import androidx.core.view.updatePadding
import androidx.lifecycle.ViewModelProvider
import ca.rmen.android.poetassistant.BuildConfig
import ca.rmen.android.poetassistant.Constants
import ca.rmen.android.poetassistant.Favorites
Expand All @@ -56,6 +57,7 @@ import ca.rmen.android.poetassistant.main.dictionaries.rt.OnWordClickListener
import ca.rmen.android.poetassistant.main.dictionaries.rt.Rhymer
import ca.rmen.android.poetassistant.main.dictionaries.rt.Thesaurus
import ca.rmen.android.poetassistant.main.dictionaries.search.Search
import ca.rmen.android.poetassistant.main.dictionaries.search.SuggestionsViewModel
import ca.rmen.android.poetassistant.main.reader.ReaderFragment
import ca.rmen.android.poetassistant.settings.SettingsActivity
import ca.rmen.android.poetassistant.settings.SettingsPrefs
Expand Down Expand Up @@ -134,6 +136,18 @@ class MainActivity : AppCompatActivity(), OnWordClickListener, WarningNoSpaceDia
top = insets.top,
)
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (mBinding.appBarLayout.top < 0) {
mBinding.appBarLayout.setExpanded(true, true)
} else {
finish()
}
}
})
val searchView = mBinding.searchView
val suggestionsViewModel = ViewModelProvider(this).get(SuggestionsViewModel::class.java)
mSearch.setSearchView(searchView, suggestionsViewModel)
}

override fun onResume() {
Expand All @@ -146,11 +160,6 @@ class MainActivity : AppCompatActivity(), OnWordClickListener, WarningNoSpaceDia
AppBarLayoutHelper.forceExpandAppBarLayout(mBinding.appBarLayout)
}

override fun onBackPressed() {
Log.v(TAG, "onBackPressed")
super.onBackPressed()
}

override fun onPause() {
Log.v(TAG, "onPause")
super.onPause()
Expand Down Expand Up @@ -224,8 +233,6 @@ class MainActivity : AppCompatActivity(), OnWordClickListener, WarningNoSpaceDia
override fun onCreateOptionsMenu(menu: Menu): Boolean {
Log.d(TAG, "onCreateOptionsMenu, menu=$menu")
menuInflater.inflate(R.menu.menu_main, menu)
val searchView = menu.findItem(R.id.action_search).actionView as SearchView
mSearch.setSearchView(searchView)
return super.onCreateOptionsMenu(menu)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ class FilterDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.v(TAG, "onCreateDialog: savedInstanceState=$savedInstanceState")
activity?.let { context ->
val themedLayoutInflater = LayoutInflater.from(ContextThemeWrapper(context, R.style.AppAlertDialog))
val binding = DataBindingUtil.inflate<InputDialogEditTextBinding>(themedLayoutInflater,
val layoutInflater = LayoutInflater.from(context)
val binding = DataBindingUtil.inflate<InputDialogEditTextBinding>(layoutInflater,
R.layout.input_dialog_edit_text, null, false)
arguments?.let { args ->
binding.edit.setText(args.getString(EXTRA_TEXT))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import android.os.Bundle
import androidx.fragment.app.Fragment
import android.util.Log
import android.view.View
import androidx.annotation.IdRes
import ca.rmen.android.poetassistant.Constants
import ca.rmen.android.poetassistant.R
import ca.rmen.android.poetassistant.TtsState
Expand Down Expand Up @@ -201,6 +202,21 @@ object ResultListFactory {
}
}

/**
* Determine if the matched word in the results list header should be selectable
* (for copying/pasting).
*
* If we make the matched word always selectable, this causes issues with the
* app bar: When loading the app, the content scrolls up, partially hiding the
* top bar.
*
* Maybe there's a better way to avoid this scrolling. But in any case, it doesn't
* make sense for the text to be selectable if it's empty, or if it's just a "label"
* as in the case of the favorites result list header.
*/
fun getMatchedWordSelectability(tab: Tab, matchedWord: String) =
matchedWord.isNotBlank() && tab != Tab.FAVORITES

fun getTabName(context: Context, tab: Tab): String {
return when (tab) {
Tab.PATTERN -> context.getString(R.string.tab_pattern)
Expand All @@ -212,4 +228,14 @@ object ResultListFactory {
else -> context.getString(R.string.tab_reader)
}
}
@IdRes
fun getRecyclerViewId(tab: Tab): Int = when (tab) {
Tab.PATTERN -> R.id.pattern_recycler_view
Tab.FAVORITES -> R.id.favorites_recycler_view
Tab.WOTD -> R.id.wotd_recycler_view
Tab.RHYMER -> R.id.rhymer_recycler_view
Tab.THESAURUS -> R.id.thesaurus_recycler_view
Tab.DICTIONARY -> R.id.dictionary_recycler_view
else -> R.id.recycler_view
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class ResultListFragment<out T: Any> : Fragment() {
rightMargin = insets.right
}
}
mBinding.recyclerView.id = ResultListFactory.getRecyclerViewId(it)
@Suppress("UNCHECKED_CAST")
mViewModel = ResultListFactory.createViewModel(it, this) as ResultListViewModel<T>
mBinding.viewModel = mViewModel
Expand Down Expand Up @@ -221,7 +222,12 @@ class ResultListFragment<out T: Any> : Fragment() {

private val mFavoritesObserver = Observer<List<Favorite>> { reload() }

private val mUsedQueryWordChanged = Observer<String> { usedQueryWord -> mHeaderViewModel.query.set(usedQueryWord) }
private val mUsedQueryWordChanged = Observer<String> { usedQueryWord ->
mHeaderViewModel.query.set(usedQueryWord)
mTab?.let {
mHeaderViewModel.isMatchedWordSelectable.set(ResultListFactory.getMatchedWordSelectability(it, usedQueryWord))
}
}

private val mEmptyTextObserver = Observer<EmptyText> { emptyText ->
mBinding.empty.text = when (emptyText) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import javax.inject.Inject

class ResultListHeaderViewModel(application: Application) : AndroidViewModel(application) {
val query = ObservableField<String>()
val isMatchedWordSelectable = ObservableField(false)
val filter = ObservableField<String>()
val isFavorite = ObservableBoolean()
val showHeader = ObservableBoolean()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ package ca.rmen.android.poetassistant.main.dictionaries.search

import android.app.Activity
import android.app.SearchManager
import android.content.ComponentName
import android.content.ContentValues
import android.content.Context
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import androidx.appcompat.widget.SearchView
import android.util.Log
import ca.rmen.android.poetassistant.Constants
import ca.rmen.android.poetassistant.R
import ca.rmen.android.poetassistant.Threading
Expand All @@ -36,7 +35,10 @@ import ca.rmen.android.poetassistant.main.PagerAdapter
import ca.rmen.android.poetassistant.main.Tab
import ca.rmen.android.poetassistant.main.dictionaries.ResultListFragment
import ca.rmen.android.poetassistant.main.dictionaries.dictionary.Dictionary
import ca.rmen.android.poetassistant.widget.DebounceTextWatcher
import ca.rmen.android.poetassistant.widget.ViewShownScheduler
import com.google.android.material.search.SearchView
import kotlinx.coroutines.launch
import java.util.Locale
import javax.inject.Inject

Expand Down Expand Up @@ -65,11 +67,45 @@ class Search constructor(private val searchableActivity: Activity, private val v
mPagerAdapter = viewPager.adapter as PagerAdapter
}

fun setSearchView(searchView: SearchView) {
(searchableActivity.getSystemService(Context.SEARCH_SERVICE) as SearchManager?)?.let {
val searchableActivityComponentName = ComponentName(searchableActivity, searchableActivity.javaClass)
searchView.queryHint = searchableActivity.getString(R.string.search_hint) // To hopefully prevent some crashes (!!) :(
searchView.setSearchableInfo(it.getSearchableInfo(searchableActivityComponentName))
fun setSearchView(searchView: SearchView, suggestionsViewModel: SuggestionsViewModel) {
searchView.hint =
searchableActivity.getString(R.string.search_hint) // To hopefully prevent some crashes (!!) :(
// Step 1: Setup suggestions
val suggestionsList: RecyclerView = searchView.findViewById(R.id.search_suggestions_list)
val adapter = SuggestionsAdapter()
suggestionsList.adapter = adapter
// Step 1a: Fetch suggestions from the disk when the user stops typing.
DebounceTextWatcher.debounce(searchView.editText, {
val typedText = searchView.editText.text.toString()
suggestionsViewModel.fetchSuggestions(typedText)
})
// Step 2a: When the suggestions list is updated, notify the recycler view adapter.
suggestionsViewModel.viewModelScope.launch {
suggestionsViewModel.suggestions.collect { value ->
Log.d(TAG, "Emitted $value")
adapter.suggestions.clear()
adapter.suggestions.addAll(value)
adapter.notifyDataSetChanged()
}
}

// Step 2: Listen for search events:
fun searchTermSelected(searchTerm: String) {
searchView.hide()
if (searchTerm.isNotBlank()) {
addSuggestions(searchTerm)
search(searchTerm)
}
}

// Case 2a: Handle when the user taps enter from the search widget
searchView.editText.setOnEditorActionListener { _, _, _ ->
searchTermSelected(searchView.editText.text.toString())
false
}
// Case 2b: Handle when the user clicked on a search suggestion.
adapter.listener = { value ->
searchTermSelected(value)
}
}

Expand Down
Loading

0 comments on commit b58e175

Please sign in to comment.