1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-01-24 16:07:04 +00:00

searchfilters: replace old filter interaction and integrate new dialog into SearchFragment

There is also a configuration option to choose between different search
UI's
This commit is contained in:
evermind 2022-08-19 00:19:25 +02:00
parent 94511671cf
commit 6bcca69563
6 changed files with 187 additions and 111 deletions

View File

@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments.list.search;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags; import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
import static java.util.Arrays.asList;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@ -33,16 +32,18 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat; import androidx.appcompat.widget.TooltipCompat;
import androidx.collection.SparseArrayCompat;
import androidx.core.text.HtmlCompat; import androidx.core.text.HtmlCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentSearchBinding; import org.schabi.newpipe.databinding.FragmentSearchBinding;
import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
@ -53,10 +54,13 @@ import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo; import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory; import org.schabi.newpipe.extractor.search.filter.FilterItem;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory;
import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.fragments.list.search.filter.SearchFilterChipDialogFragment;
import org.schabi.newpipe.fragments.list.search.filter.SearchFilterDialogFragment;
import org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic;
import org.schabi.newpipe.fragments.list.search.filter.SearchFilterOptionMenuAlikeDialogFragment;
import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
@ -66,7 +70,6 @@ import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.KeyboardUtil; import org.schabi.newpipe.util.KeyboardUtil;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -104,9 +107,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
private static final int SUGGESTIONS_DEBOUNCE = 120; //ms private static final int SUGGESTIONS_DEBOUNCE = 120; //ms
private final PublishSubject<String> suggestionPublisher = PublishSubject.create(); private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
@State
int filterItemCheckedId = -1;
@State @State
protected int serviceId = Constants.NO_SERVICE_ID; protected int serviceId = Constants.NO_SERVICE_ID;
@ -114,15 +114,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State @State
String searchString; String searchString;
/** List<FilterItem> selectedContentFilter = new ArrayList<>();
* No content filter should add like contentFilter = all
* be aware of this when implementing an extractor.
*/
@State
String[] contentFilter = new String[0];
@State List<FilterItem> selectedSortFilter = new ArrayList<>();
String sortFilter;
// these represents the last search // these represents the last search
@State @State
@ -140,8 +134,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State @State
boolean wasSearchFocused = false; boolean wasSearchFocused = false;
private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>();
private StreamingService service;
private Page nextPage; private Page nextPage;
private boolean showLocalSuggestions = true; private boolean showLocalSuggestions = true;
private boolean showRemoteSuggestions = true; private boolean showRemoteSuggestions = true;
@ -159,7 +151,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
private FragmentSearchBinding searchBinding; private FragmentSearchBinding searchBinding;
private View searchToolbarContainer; protected View searchToolbarContainer;
private EditText searchEditText; private EditText searchEditText;
private View searchClear; private View searchClear;
@ -173,9 +165,32 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
*/ */
private TextWatcher textWatcher; private TextWatcher textWatcher;
@State
ArrayList<Integer> userSelectedContentFilterList;
@State
ArrayList<Integer> userSelectedSortFilterList = null;
protected SearchViewModel searchViewModel;
protected SearchFilterLogic.Factory.Variant logicVariant =
SearchFilterLogic.Factory.Variant.SEARCH_FILTER_LOGIC_DEFAULT;
public static SearchFragment getInstance(final int serviceId, final String searchString) { public static SearchFragment getInstance(final int serviceId, final String searchString) {
final SearchFragment searchFragment = new SearchFragment(); final SearchFragment searchFragment;
searchFragment.setQuery(serviceId, searchString, new String[0], ""); final App app = App.getApp();
final String searchUi = PreferenceManager.getDefaultSharedPreferences(app)
.getString(app.getString(R.string.search_filter_ui_key),
app.getString(R.string.search_filter_ui_value));
if (app.getString(R.string.search_filter_ui_option_menu_legacy_key).equals(searchUi)) {
searchFragment = new SearchFragmentLegacy();
} else {
searchFragment = new SearchFragment();
}
searchFragment.setQuery(serviceId, searchString);
if (!TextUtils.isEmpty(searchString)) { if (!TextUtils.isEmpty(searchString)) {
searchFragment.setSearchOnResume(); searchFragment.setSearchOnResume();
@ -208,11 +223,53 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} }
@Override @Override
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) { @Nullable final Bundle savedInstanceState) {
if (userSelectedContentFilterList == null) {
userSelectedContentFilterList = new ArrayList<>();
}
if (userSelectedSortFilterList == null) {
userSelectedSortFilterList = new ArrayList<>();
}
initViewModel();
// observe the content/sort filter items lists
searchViewModel.getSelectedContentFilterItemListLiveData().observe(
getViewLifecycleOwner(), filterItems -> selectedContentFilter = filterItems);
searchViewModel.getSelectedSortFilterItemListLiveData().observe(
getViewLifecycleOwner(), filterItems -> selectedSortFilter = filterItems);
// the content/sort filters ids lists are only
// observed here to store them via Icepick
searchViewModel.getUserSelectedContentFilterListLiveData().observe(
getViewLifecycleOwner(), filterIds -> userSelectedContentFilterList = filterIds);
searchViewModel.getUserSelectedSortFilterListLiveData().observe(
getViewLifecycleOwner(), filterIds -> userSelectedSortFilterList = filterIds);
searchViewModel.getDoSearchLiveData().observe(
getViewLifecycleOwner(), doSearch -> {
if (doSearch) {
selectedFilters(selectedContentFilter, selectedSortFilter);
searchViewModel.weConsumedDoSearchLiveData();
}
});
return inflater.inflate(R.layout.fragment_search, container, false); return inflater.inflate(R.layout.fragment_search, container, false);
} }
protected void initViewModel() {
searchViewModel = new ViewModelProvider(this, SearchViewModel.Companion
.getFactory(serviceId,
logicVariant,
userSelectedContentFilterList,
userSelectedSortFilterList))
.get(SearchViewModel.class);
}
@Override @Override
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
searchBinding = FragmentSearchBinding.bind(rootView); searchBinding = FragmentSearchBinding.bind(rootView);
@ -221,22 +278,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
initSearchListeners(); initSearchListeners();
} }
private void updateService() {
try {
service = NewPipe.getService(serviceId);
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Getting service for id " + serviceId, e);
}
}
@Override @Override
public void onStart() { public void onStart() {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onStart() called"); Log.d(TAG, "onStart() called");
} }
super.onStart(); super.onStart();
updateService();
} }
@Override @Override
@ -268,11 +315,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (!TextUtils.isEmpty(searchString)) { if (!TextUtils.isEmpty(searchString)) {
if (wasLoading.getAndSet(false)) { if (wasLoading.getAndSet(false)) {
search(searchString, contentFilter, sortFilter); search(searchString);
return; return;
} else if (infoListAdapter.getItemsList().isEmpty()) { } else if (infoListAdapter.getItemsList().isEmpty()) {
if (savedState == null) { if (savedState == null) {
search(searchString, contentFilter, sortFilter); search(searchString);
return; return;
} else if (!isLoading.get() && !wasSearchFocused && lastPanelError == null) { } else if (!isLoading.get() && !wasSearchFocused && lastPanelError == null) {
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
@ -325,7 +372,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (requestCode == ReCaptchaActivity.RECAPTCHA_REQUEST) { if (requestCode == ReCaptchaActivity.RECAPTCHA_REQUEST) {
if (resultCode == Activity.RESULT_OK if (resultCode == Activity.RESULT_OK
&& !TextUtils.isEmpty(searchString)) { && !TextUtils.isEmpty(searchString)) {
search(searchString, contentFilter, sortFilter); search(searchString);
} else { } else {
Log.e(TAG, "ReCaptcha failed"); Log.e(TAG, "ReCaptcha failed");
} }
@ -391,6 +438,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchString = searchEditText != null searchString = searchEditText != null
? searchEditText.getText().toString() ? searchEditText.getText().toString()
: searchString; : searchString;
super.onSaveInstanceState(bundle); super.onSaveInstanceState(bundle);
} }
@ -404,7 +452,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) { || (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
search(!TextUtils.isEmpty(searchString) search(!TextUtils.isEmpty(searchString)
? searchString ? searchString
: searchEditText.getText().toString(), this.contentFilter, ""); : searchEditText.getText().toString());
} else { } else {
if (searchEditText != null) { if (searchEditText != null) {
searchEditText.setText(""); searchEditText.setText("");
@ -429,62 +477,24 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
supportActionBar.setDisplayHomeAsUpEnabled(true); supportActionBar.setDisplayHomeAsUpEnabled(true);
} }
int itemId = 0; createMenu(menu, inflater);
boolean isFirstItem = true;
final Context c = getContext();
if (service == null) {
Log.w(TAG, "onCreateOptionsMenu() called with null service");
updateService();
} }
for (final String filter : service.getSearchQHFactory().getAvailableContentFilter()) { protected void createMenu(@NonNull final Menu menu,
if (filter.equals(YoutubeSearchQueryHandlerFactory.MUSIC_SONGS)) { @NonNull final MenuInflater inflater) {
final MenuItem musicItem = menu.add(2, inflater.inflate(R.menu.menu_search_fragment, menu);
itemId++,
0,
"YouTube Music");
musicItem.setEnabled(false);
} else if (filter.equals(PeertubeSearchQueryHandlerFactory.SEPIA_VIDEOS)) {
final MenuItem sepiaItem = menu.add(2,
itemId++,
0,
"Sepia Search");
sepiaItem.setEnabled(false);
}
menuItemToFilterName.put(itemId, filter);
final MenuItem item = menu.add(1,
itemId++,
0,
ServiceHelper.getTranslatedFilterString(filter, c));
if (isFirstItem) {
item.setChecked(true);
isFirstItem = false;
}
}
menu.setGroupCheckable(1, true, true);
restoreFilterChecked(menu, filterItemCheckedId);
} }
@Override @Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) { public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
final var filter = Collections.singletonList(menuItemToFilterName.get(item.getItemId())); if (item.getItemId() == R.id.action_filter) {
changeContentFilter(item, filter); hideKeyboardSearch();
showSelectFiltersDialog();
return false;
}
return true; return true;
} }
private void restoreFilterChecked(final Menu menu, final int itemId) {
if (itemId != -1) {
final MenuItem item = menu.findItem(itemId);
if (item == null) {
return;
}
item.setChecked(true);
}
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Search // Search
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -562,7 +572,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() { suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
@Override @Override
public void onSuggestionItemSelected(final SuggestionItem item) { public void onSuggestionItemSelected(final SuggestionItem item) {
search(item.query, new String[0], ""); search(item.query);
searchEditText.setText(item.query); searchEditText.setText(item.query);
} }
@ -619,7 +629,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} else if (event != null } else if (event != null
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) { || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
search(searchEditText.getText().toString(), new String[0], ""); search(searchEditText.getText().toString());
return true; return true;
} }
return false; return false;
@ -671,7 +681,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
KeyboardUtil.showKeyboard(activity, searchEditText); KeyboardUtil.showKeyboard(activity, searchEditText);
} }
private void hideKeyboardSearch() { protected void hideKeyboardSearch() {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "hideKeyboardSearch() called"); Log.d(TAG, "hideKeyboardSearch() called");
} }
@ -805,9 +815,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// no-op // no-op
} }
private void search(final String theSearchString, private void search(final String theSearchString) {
final String[] theContentFilter,
final String theSortFilter) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "search() called with: query = [" + theSearchString + "]"); Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
} }
@ -862,13 +870,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} }
searchDisposable = ExtractorHelper.searchFor(serviceId, searchDisposable = ExtractorHelper.searchFor(serviceId,
searchString, searchString,
Arrays.asList(contentFilter), selectedContentFilter,
sortFilter) selectedSortFilter)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnEvent((searchResult, throwable) -> isLoading.set(false)) .doOnEvent((searchResult, throwable) -> isLoading.set(false))
.subscribe(this::handleResult, this::onItemError); .subscribe(this::handleResult, this::onItemError);
} }
@Override @Override
@ -884,8 +891,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchDisposable = ExtractorHelper.getMoreSearchItems( searchDisposable = ExtractorHelper.getMoreSearchItems(
serviceId, serviceId,
searchString, searchString,
asList(contentFilter), selectedContentFilter,
sortFilter, selectedSortFilter,
nextPage) nextPage)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -917,25 +924,21 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void changeContentFilter(final MenuItem item, final List<String> theContentFilter) { public void selectedFilters(@NonNull final List<FilterItem> theSelectedContentFilter,
filterItemCheckedId = item.getItemId(); @NonNull final List<FilterItem> theSelectedSortFilter) {
item.setChecked(true);
contentFilter = theContentFilter.toArray(new String[0]); selectedContentFilter = theSelectedContentFilter;
selectedSortFilter = theSelectedSortFilter;
if (!TextUtils.isEmpty(searchString)) { if (!TextUtils.isEmpty(searchString)) {
search(searchString, contentFilter, sortFilter); search(searchString);
} }
} }
private void setQuery(final int theServiceId, private void setQuery(final int theServiceId,
final String theSearchString, final String theSearchString) {
final String[] theContentFilter,
final String theSortFilter) {
serviceId = theServiceId; serviceId = theServiceId;
searchString = theSearchString; searchString = theSearchString;
contentFilter = theContentFilter;
sortFilter = theSortFilter;
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -1020,7 +1023,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchBinding.correctSuggestion.setOnClickListener(v -> { searchBinding.correctSuggestion.setOnClickListener(v -> {
searchBinding.correctSuggestion.setVisibility(View.GONE); searchBinding.correctSuggestion.setVisibility(View.GONE);
search(searchSuggestion, contentFilter, sortFilter); search(searchSuggestion);
searchEditText.setText(searchSuggestion); searchEditText.setText(searchSuggestion);
}); });
@ -1085,4 +1088,22 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
UserAction.DELETE_FROM_HISTORY, "Deleting item failed"))); UserAction.DELETE_FROM_HISTORY, "Deleting item failed")));
disposables.add(onDelete); disposables.add(onDelete);
} }
private void showSelectFiltersDialog() {
final FragmentManager fragmentManager = getChildFragmentManager();
final DialogFragment searchFilterUiDialog;
final String searchUi = PreferenceManager.getDefaultSharedPreferences(App.getApp())
.getString(getString(R.string.search_filter_ui_key),
getString(R.string.search_filter_ui_value));
if (getString(R.string.search_filter_ui_option_menu_style_key).equals(searchUi)) {
searchFilterUiDialog = new SearchFilterOptionMenuAlikeDialogFragment();
} else if (getString(R.string.search_filter_ui_chip_dialog_key).equals(searchUi)) {
searchFilterUiDialog = new SearchFilterChipDialogFragment();
} else { // default dialog
searchFilterUiDialog = new SearchFilterDialogFragment();
}
searchFilterUiDialog.show(fragmentManager, "fragment_search");
}
} }

View File

@ -27,6 +27,8 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import org.schabi.newpipe.extractor.search.filter.FilterItem;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat; import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -74,8 +76,8 @@ public final class ExtractorHelper {
} }
public static Single<SearchInfo> searchFor(final int serviceId, final String searchString, public static Single<SearchInfo> searchFor(final int serviceId, final String searchString,
final List<String> contentFilter, final List<FilterItem> contentFilter,
final String sortFilter) { final List<FilterItem> sortFilter) {
checkServiceId(serviceId); checkServiceId(serviceId);
return Single.fromCallable(() -> return Single.fromCallable(() ->
SearchInfo.getInfo(NewPipe.getService(serviceId), SearchInfo.getInfo(NewPipe.getService(serviceId),
@ -87,8 +89,8 @@ public final class ExtractorHelper {
public static Single<InfoItemsPage<InfoItem>> getMoreSearchItems( public static Single<InfoItemsPage<InfoItem>> getMoreSearchItems(
final int serviceId, final int serviceId,
final String searchString, final String searchString,
final List<String> contentFilter, final List<FilterItem> contentFilter,
final String sortFilter, final List<FilterItem> sortFilter,
final Page page) { final Page page) {
checkServiceId(serviceId); checkServiceId(serviceId);
return Single.fromCallable(() -> return Single.fromCallable(() ->

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_filter"
android:title="@string/filter"
android:icon="@drawable/ic_sort"
app:showAsAction="always" />
</menu>

View File

@ -1383,6 +1383,28 @@
<item>@string/card</item> <item>@string/card</item>
</string-array> </string-array>
<string name="search_filter_ui_key">search_filter_ui</string>
<string name="search_filter_ui_value">@string/search_filter_ui_dialog_key</string>
<string name="search_filter_ui_dialog_key">dialog</string>
<string name="search_filter_ui_option_menu_style_key">style</string>
<string name="search_filter_ui_option_menu_legacy_key">legacy</string>
<string name="search_filter_ui_chip_dialog_key">chip</string>
<string-array name="search_filter_ui_values">
<item>@string/search_filter_ui_dialog_key</item>
<item>@string/search_filter_ui_option_menu_style_key</item>
<item>@string/search_filter_ui_option_menu_legacy_key</item>
<item>@string/search_filter_ui_chip_dialog_key</item>
</string-array>
<string-array name="search_filter_ui_description">
<item>@string/search_filter_ui_dialog</item>
<item>@string/search_filter_ui_style</item>
<item>@string/search_filter_ui_legacy</item>
<item>@string/search_filter_ui_chip_dialog</item>
</string-array>
<string name="tablet_mode_key">tablet_mode</string> <string name="tablet_mode_key">tablet_mode</string>
<string name="tablet_mode_auto_key">auto</string> <string name="tablet_mode_auto_key">auto</string>

View File

@ -907,4 +907,16 @@
<string name="search_filters_yes">Yes</string> <string name="search_filters_yes">Yes</string>
<string name="search_filters_youtube_music">YouTube Music</string> <string name="search_filters_youtube_music">YouTube Music</string>
<!-- end - strings for search filter feature (NewPipeExtractor Services) --> <!-- end - strings for search filter feature (NewPipeExtractor Services) -->
<!-- begin - strings for search filter UI -->
<string name="filter">Filter</string>
<string name="filter_search_sort_filters">Sort filters</string>
<string name="filter_search_content_filters">Content filters</string>
<string name="search_filter_ui">Select Search Filter UI</string>
<string name="search_filter_ui_dialog">Simple Dialog (default)</string>
<string name="search_filter_ui_style">Action Menu styled Dialog</string>
<string name="search_filter_ui_legacy">Action Menu (legacy)</string>
<string name="search_filter_ui_chip_dialog">Chip Dialog</string>
<!-- end - strings for search filter UI -->
</resources> </resources>

View File

@ -66,6 +66,16 @@
app:singleLineTitle="false" app:singleLineTitle="false"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="@string/search_filter_ui_dialog_key"
android:entries="@array/search_filter_ui_description"
android:entryValues="@array/search_filter_ui_values"
android:key="@string/search_filter_ui_key"
android:summary="%s"
android:title="@string/search_filter_ui"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:key="@string/main_tabs_position_key" android:key="@string/main_tabs_position_key"