diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragmentLegacy.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragmentLegacy.java new file mode 100644 index 000000000..7186983e2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragmentLegacy.java @@ -0,0 +1,73 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search; + +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic; +import org.schabi.newpipe.fragments.list.search.filter.SearchFilterUIOptionMenu; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import icepick.State; + +/** + * Fragment that hosts the action menu based filter 'dialog'. + *
+ * Called ..Legacy because this was the way NewPipe had implemented the search filter dialog. + *
+ * The new UI's are handled by {@link SearchFragment} and implemented by + * using {@link androidx.fragment.app.DialogFragment}. + */ +public class SearchFragmentLegacy extends SearchFragment { + + @State + protected int countOnPrepareOptionsMenuCalls = 0; + private SearchFilterUIOptionMenu searchFilterUi; + + @Override + protected void initViewModel() { + logicVariant = SearchFilterLogic.Factory.Variant.SEARCH_FILTER_LOGIC_LEGACY; + super.initViewModel(); + + searchFilterUi = new SearchFilterUIOptionMenu( + searchViewModel.getSearchFilterLogic(), requireContext()); + } + + @Override + protected void createMenu(@NonNull final Menu menu, + @NonNull final MenuInflater inflater) { + searchFilterUi.createSearchUI(menu); + } + + @Override + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + return searchFilterUi.onOptionsItemSelected(item); + } + + @Override + protected void initViews(final View rootView, + final Bundle savedInstanceState) { + super.initViews(rootView, savedInstanceState); + final Toolbar toolbar = (Toolbar) searchToolbarContainer.getParent(); + toolbar.setOverflowIcon(ContextCompat.getDrawable(requireContext(), + R.drawable.ic_sort)); + } + + @Override + public void onPrepareOptionsMenu(@NonNull final Menu menu) { + super.onPrepareOptionsMenu(menu); + // workaround: we want to hide the keyboard in case we open the options + // menu. As somehow this method gets triggered twice but only the 2nd + // time is relevant as the options menu is selected by the user. + if (++countOnPrepareOptionsMenuCalls > 1) { + hideKeyboardSearch(); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/SearchFilterUIOptionMenu.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/SearchFilterUIOptionMenu.java new file mode 100644 index 000000000..605c15daf --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/SearchFilterUIOptionMenu.java @@ -0,0 +1,303 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search.filter; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.search.filter.FilterContainer; +import org.schabi.newpipe.extractor.search.filter.FilterGroup; +import org.schabi.newpipe.extractor.search.filter.FilterItem; +import org.schabi.newpipe.extractor.search.filter.LibraryStringIds; +import org.schabi.newpipe.util.ServiceHelper; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.appcompat.view.menu.MenuBuilder; +import androidx.core.view.MenuCompat; + +import static android.content.ContentValues.TAG; +import static org.schabi.newpipe.fragments.list.search.filter.InjectFilterItem.DividerItem; +import static org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic.ICreateUiForFiltersWorker; +import static org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic.IUiItemWrapper; + +/** + * The implementation of the action menu based 'dialog'. + */ +public class SearchFilterUIOptionMenu extends BaseSearchFilterUiGenerator { + + // Menu groups identifier + private static final int MENU_GROUP_SEARCH_RESET_BUTTONS = 0; + // give them negative ids to not conflict with the ids of the filters + private static final int MENU_ID_SEARCH_BUTTON = -100; + private static final int MENU_ID_RESET_BUTTON = -101; + private Menu menu = null; + // initialize with first group id -> next group after the search/reset buttons group + private int newLastUsedGroupId = MENU_GROUP_SEARCH_RESET_BUTTONS + 1; + private int firstSortFilterGroupId; + + public SearchFilterUIOptionMenu( + @NonNull final SearchFilterLogic logic, + @NonNull final Context context) { + super(logic, context); + } + + int getLastUsedGroupIdThanIncrement() { + return newLastUsedGroupId++; + } + + @SuppressLint("RestrictedApi") + private void alwaysShowMenuItemIcon(final Menu theMenu) { + // always show icons + if (theMenu instanceof MenuBuilder) { + final MenuBuilder builder = ((MenuBuilder) theMenu); + builder.setOptionalIconsVisible(true); + } + } + + public void createSearchUI(@NonNull final Menu theMenu) { + this.menu = theMenu; + alwaysShowMenuItemIcon(theMenu); + + createSearchUI(); + + MenuCompat.setGroupDividerEnabled(theMenu, true); + } + + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + if (item.getGroupId() == MENU_GROUP_SEARCH_RESET_BUTTONS + && item.getItemId() == MENU_ID_SEARCH_BUTTON) { + logic.prepareForSearch(); + } else { // all other menu groups -> reset, content filters and sort filters + + // main part for holding onto the menu -> not closing it + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + item.setActionView(new View(context)); + item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { + + @Override + public boolean onMenuItemActionExpand(final MenuItem item) { + if (item.getGroupId() == MENU_GROUP_SEARCH_RESET_BUTTONS + && item.getItemId() == MENU_ID_RESET_BUTTON) { + logic.reset(); + } else if (item.getGroupId() < firstSortFilterGroupId) { // content filters + final int filterId = item.getItemId(); + logic.selectContentFilter(filterId); + } else { // the sort filters + Log.d(TAG, "onMenuItemActionExpand: sort filters are here"); + logic.selectSortFilter(item.getItemId()); + } + + return false; + } + + @Override + public boolean onMenuItemActionCollapse(final MenuItem item) { + return false; + } + }); + } + + return false; + } + + @Override + protected ICreateUiForFiltersWorker createSortFilterWorker() { + return new CreateSortFilterUI(); + } + + @Override + protected ICreateUiForFiltersWorker createContentFilterWorker() { + return new CreateContentFilterUI(); + } + + private static class UiItemWrapper implements IUiItemWrapper { + + private final MenuItem item; + + UiItemWrapper(final MenuItem item) { + this.item = item; + } + + @Override + public void setVisible(final boolean visible) { + item.setVisible(visible); + } + + @Override + public int getItemId() { + return item.getItemId(); + } + + @Override + public boolean isChecked() { + return item.isChecked(); + } + + @Override + public void setChecked(final boolean checked) { + item.setChecked(checked); + } + } + + private class CreateContentFilterUI implements ICreateUiForFiltersWorker { + + /** + * MenuItem's that should not be checkable. + */ + final List