From e8c3892c7d3adf017d0ff0e9c9e5a2d138f3eb3c Mon Sep 17 00:00:00 2001 From: VougJo23 Date: Sun, 8 Jun 2025 13:38:06 +0300 Subject: [PATCH] Display fragment based on search text Keep Main Fragment until user's input --- .../fragments/list/search/SearchFragment.java | 32 +++---- .../newpipe/settings/SettingsActivity.java | 92 ++++++++++++++++--- .../preferencesearch/PreferenceSearcher.java | 6 -- .../settings_preferencesearch_fragment.xml | 1 + .../settings/SettingsActivityTest.java | 71 ++++++++++++++ 5 files changed, 165 insertions(+), 37 deletions(-) create mode 100644 app/src/test/java/org/schabi/newpipe/settings/SettingsActivityTest.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 18c60400b..0f52955df 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -764,14 +764,14 @@ public class SearchFragment extends BaseListFragment { - remote.removeIf(remoteItem -> local.stream().anyMatch( - localItem -> localItem.equals(remoteItem))); - local.addAll(remote); - return local; - }) + getLocalSuggestionsObservable(query, 3), + getRemoteSuggestionsObservable(query), + (local, remote) -> { + remote.removeIf(remoteItem -> local.stream().anyMatch( + localItem -> localItem.equals(remoteItem))); + local.addAll(remote); + return local; + }) .materialize(); } else if (showLocalSuggestions) { return getLocalSuggestionsObservable(query, 25) @@ -876,9 +876,9 @@ public class SearchFragment extends BaseListFragment isLoading.set(false)) @@ -897,11 +897,11 @@ public class SearchFragment extends BaseListFragment isLoading.set(false)) diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index 0d57ce174..dc4e79bc1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -22,7 +22,6 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import com.evernote.android.state.State; -import com.jakewharton.rxbinding4.widget.RxTextView; import com.livefront.bridge.Bridge; import org.schabi.newpipe.MainActivity; @@ -43,6 +42,10 @@ import org.schabi.newpipe.views.FocusOverlayView; import java.util.concurrent.TimeUnit; +import io.reactivex.rxjava3.disposables.Disposable; + +import com.jakewharton.rxbinding4.widget.RxTextView; + /* * Created by Christian Schabesberger on 31.08.15. * @@ -86,6 +89,8 @@ public class SettingsActivity extends AppCompatActivity implements @State boolean wasSearchActive; + private Disposable searchDisposable; + @Override protected void onCreate(final Bundle savedInstanceBundle) { setTheme(ThemeHelper.getSettingsThemeStyle(this)); @@ -111,9 +116,7 @@ public class SettingsActivity extends AppCompatActivity implements } } } else { - getSupportFragmentManager().beginTransaction() - .replace(R.id.settings_fragment_holder, new MainSettingsFragment()) - .commit(); + showMainSettingsFragment(); } if (DeviceUtils.isTv(this)) { @@ -189,8 +192,18 @@ public class SettingsActivity extends AppCompatActivity implements .commit(); } + private void showMainSettingsFragment() { + getSupportFragmentManager().beginTransaction() + .replace(R.id.settings_fragment_holder, new MainSettingsFragment()) + .commit(); + } + + @Override protected void onDestroy() { + if (searchDisposable != null && !searchDisposable.isDisposed()) { + searchDisposable.dispose(); + } setMenuSearchItem(null); searchFragment = null; super.onDestroy(); @@ -205,16 +218,43 @@ public class SettingsActivity extends AppCompatActivity implements final SettingsLayoutBinding settingsLayoutBinding, final boolean restored ) { + searchContainer = settingsLayoutBinding.settingsToolbarLayout.toolbar .findViewById(R.id.toolbar_search_container); // Configure input field for search searchEditText = searchContainer.findViewById(R.id.toolbar_search_edit_text); - RxTextView.textChanges(searchEditText) + searchDisposable = RxTextView.textChanges(searchEditText) + // Ignore the initial empty state + .skipInitialValue() // Wait some time after the last input before actually searching .debounce(200, TimeUnit.MILLISECONDS) - .subscribe(v -> runOnUiThread(this::onSearchChanged)); + .subscribe(charSequence -> runOnUiThread(() -> { + // Change Fragment based on search text + if (charSequence.length() > 0) { + if (searchFragment != null) { + if (!searchFragment.isAdded()) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.settings_fragment_holder, searchFragment, + PreferenceSearchFragment.NAME) + .addToBackStack(PreferenceSearchFragment.NAME) + .commit(); + } else { + showSettingsFragment(searchFragment); + } + onSearchChanged(); + } + } else { + final Fragment current = getSupportFragmentManager() + .findFragmentById(R.id.settings_fragment_holder); + if (!(current instanceof MainSettingsFragment)) { + getSupportFragmentManager() + .popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); + showMainSettingsFragment(); + } + } + })); // Configure clear button searchContainer.findViewById(R.id.toolbar_search_clear) @@ -294,25 +334,36 @@ public class SettingsActivity extends AppCompatActivity implements Log.d(TAG, "setSearchActive called active=" + active); } + // Only reset the text when activating search from the toolbar + if (active && !isSearchActive()) { + resetSearchText(); + } + // Ignore if search is already in correct state if (isSearchActive() == active) { return; } wasSearchActive = active; + if (wasSearchActive) { + searchEditText.post(() -> { + searchEditText.requestFocus(); + KeyboardUtil.showKeyboard(this, searchEditText); + }); + } searchContainer.setVisibility(active ? View.VISIBLE : View.GONE); if (menuSearchItem != null) { menuSearchItem.setVisible(!active); } - if (active) { - getSupportFragmentManager() - .beginTransaction() - .add(FRAGMENT_HOLDER_ID, searchFragment, PreferenceSearchFragment.NAME) - .addToBackStack(PreferenceSearchFragment.NAME) - .commit(); - + if (active && searchText == null) { + showMainSettingsFragment(); + } else if (active) { + // Only add if not already added + if (!searchFragment.isAdded()) { + showSettingsFragment(searchFragment); + } KeyboardUtil.showKeyboard(this, searchEditText); } else if (searchFragment != null) { hideSearchFragment(); @@ -323,8 +374,6 @@ public class SettingsActivity extends AppCompatActivity implements KeyboardUtil.hideKeyboard(this, searchEditText); } - - resetSearchText(); } private void hideSearchFragment() { @@ -388,5 +437,18 @@ public class SettingsActivity extends AppCompatActivity implements } } + // Functions needed for testing + protected EditText getSearchEditText() { + return searchEditText; + } + + public void setMockEditText(final EditText editText) { + this.searchEditText = editText; + } + + protected static int getFragmentHolderId() { + return FRAGMENT_HOLDER_ID; + } + //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java index b3efc8dd1..2c1abe3dd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java @@ -1,9 +1,6 @@ package org.schabi.newpipe.settings.preferencesearch; -import android.text.TextUtils; - import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -21,9 +18,6 @@ public class PreferenceSearcher { } List searchFor(final String keyword) { - if (TextUtils.isEmpty(keyword)) { - return Collections.emptyList(); - } return configuration.getSearcher() .search(allEntries.stream(), keyword) diff --git a/app/src/main/res/layout/settings_preferencesearch_fragment.xml b/app/src/main/res/layout/settings_preferencesearch_fragment.xml index 89a25b217..a3f1da523 100644 --- a/app/src/main/res/layout/settings_preferencesearch_fragment.xml +++ b/app/src/main/res/layout/settings_preferencesearch_fragment.xml @@ -18,6 +18,7 @@ android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" + android:clickable="true" android:visibility="gone" tools:visibility="gone"> diff --git a/app/src/test/java/org/schabi/newpipe/settings/SettingsActivityTest.java b/app/src/test/java/org/schabi/newpipe/settings/SettingsActivityTest.java new file mode 100644 index 000000000..8155970f4 --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/settings/SettingsActivityTest.java @@ -0,0 +1,71 @@ +package org.schabi.newpipe.settings; + +import android.widget.EditText; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchFragment; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +public class SettingsActivityTest { + + private SettingsActivity activity; + private FragmentManager fragmentManager; + + @Before + public void setUp() { + activity = Mockito.spy(new SettingsActivity()); + fragmentManager = Mockito.mock(FragmentManager.class); + Mockito.doReturn(fragmentManager).when(activity).getSupportFragmentManager(); + // Mock the searchEditText + final EditText editText = Mockito.mock(EditText.class); + activity.setMockEditText(editText); + } + + @Test + public void mainSettingsFragmentDisplayedWhenSearchInactive() { + when(fragmentManager.findFragmentById(anyInt())) + .thenReturn(new MainSettingsFragment()); + + activity.setSearchActive(false); + + final Fragment fragment = activity.getSupportFragmentManager() + .findFragmentById(SettingsActivity.getFragmentHolderId()); + assertTrue(fragment instanceof MainSettingsFragment); + } + + @Test + public void preferenceSearchFragmentDisplayedWhenSearchActiveAndTextNotEmpty() { + when(fragmentManager.findFragmentById(anyInt())) + .thenReturn(new PreferenceSearchFragment()); + + activity.setSearchActive(true); + Mockito.when(activity.getSearchEditText().getText()) + .thenReturn(new android.text.SpannableStringBuilder("query")); + + final Fragment fragment = activity.getSupportFragmentManager() + .findFragmentById(SettingsActivity.getFragmentHolderId()); + assertTrue(fragment instanceof PreferenceSearchFragment); + } + + @Test + public void mainSettingsFragmentDisplayedWhenSearchActiveButTextEmpty() { + when(fragmentManager.findFragmentById(anyInt())) + .thenReturn(new MainSettingsFragment()); + + activity.setSearchActive(true); + Mockito.when(activity.getSearchEditText().getText()) + .thenReturn(new android.text.SpannableStringBuilder("")); + + final Fragment fragment = activity.getSupportFragmentManager() + .findFragmentById(SettingsActivity.getFragmentHolderId()); + assertTrue(fragment instanceof MainSettingsFragment); + } +}