mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-25 08:26:57 +00:00
searchfilters: 3rd Ui: action based UI (enhanched legacy menu)
This approach is more or less a hack but if all else fails. Could later be dropped or right away.
This commit is contained in:
parent
466ddb60c0
commit
a0d576ffc3
@ -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'.
|
||||||
|
* <p>
|
||||||
|
* Called ..Legacy because this was the way NewPipe had implemented the search filter dialog.
|
||||||
|
* <p>
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<MenuItem> nonCheckableMenuItems = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Menu#setGroupCheckable(int, boolean, boolean)} makes all {@link MenuItem}
|
||||||
|
* checkable.
|
||||||
|
* <p>
|
||||||
|
* We do not want a group header or a group divider to be checkable. Therefore this method
|
||||||
|
* calls above mentioned method and afterwards makes all items uncheckable that are placed
|
||||||
|
* inside {@link #nonCheckableMenuItems}.
|
||||||
|
*
|
||||||
|
* @param isOnlyOneCheckable is in group only one selection allowed.
|
||||||
|
* @param groupId which group should be affected
|
||||||
|
*/
|
||||||
|
private void makeAllowedMenuItemInGroupCheckable(final boolean isOnlyOneCheckable,
|
||||||
|
final int groupId) {
|
||||||
|
// this method makes all MenuItem's checkable
|
||||||
|
menu.setGroupCheckable(groupId, true, isOnlyOneCheckable);
|
||||||
|
// uncheckable unwanted
|
||||||
|
for (final MenuItem uncheckableItem : nonCheckableMenuItems) {
|
||||||
|
if (uncheckableItem != null) {
|
||||||
|
uncheckableItem.setCheckable(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nonCheckableMenuItems.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
// create the search button
|
||||||
|
menu.add(MENU_GROUP_SEARCH_RESET_BUTTONS,
|
||||||
|
MENU_ID_SEARCH_BUTTON,
|
||||||
|
0,
|
||||||
|
context.getString(R.string.search))
|
||||||
|
.setEnabled(true)
|
||||||
|
.setCheckable(false)
|
||||||
|
.setIcon(R.drawable.ic_search);
|
||||||
|
|
||||||
|
menu.add(MENU_GROUP_SEARCH_RESET_BUTTONS,
|
||||||
|
MENU_ID_RESET_BUTTON,
|
||||||
|
0,
|
||||||
|
context.getString(R.string.playback_reset))
|
||||||
|
.setEnabled(true)
|
||||||
|
.setCheckable(false)
|
||||||
|
.setIcon(R.drawable.ic_settings_backup_restore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createFilterGroupBeforeItems(
|
||||||
|
@NonNull final FilterGroup filterGroup) {
|
||||||
|
if (filterGroup.getNameId() != null) {
|
||||||
|
createNotEnabledAndUncheckableGroupTitleMenuItem(
|
||||||
|
FilterContainer.ITEM_IDENTIFIER_UNKNOWN, filterGroup.getNameId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MenuItem createNotEnabledAndUncheckableGroupTitleMenuItem(
|
||||||
|
final int identifier,
|
||||||
|
final LibraryStringIds nameId) {
|
||||||
|
final MenuItem item = menu.add(
|
||||||
|
newLastUsedGroupId,
|
||||||
|
identifier,
|
||||||
|
0,
|
||||||
|
ServiceHelper.getTranslatedFilterString(nameId, context));
|
||||||
|
item.setEnabled(false);
|
||||||
|
|
||||||
|
nonCheckableMenuItems.add(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createFilterItem(@NonNull final FilterItem filterItem,
|
||||||
|
@NonNull final FilterGroup filterGroup) {
|
||||||
|
final MenuItem item = createMenuItem(filterItem);
|
||||||
|
|
||||||
|
if (filterItem instanceof DividerItem) {
|
||||||
|
final DividerItem dividerItem = (DividerItem) filterItem;
|
||||||
|
final String menuDividerTitle = ">>>"
|
||||||
|
+ context.getString(dividerItem.getStringResId())
|
||||||
|
+ "<<<";
|
||||||
|
item.setTitle(menuDividerTitle);
|
||||||
|
item.setEnabled(false);
|
||||||
|
nonCheckableMenuItems.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
logic.addContentFilterUiWrapperToItemMap(filterItem.getIdentifier(),
|
||||||
|
new UiItemWrapper(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MenuItem createMenuItem(final FilterItem filterItem) {
|
||||||
|
return menu.add(newLastUsedGroupId,
|
||||||
|
filterItem.getIdentifier(),
|
||||||
|
0,
|
||||||
|
ServiceHelper.getTranslatedFilterString(filterItem.getNameId(), context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createFilterGroupAfterItems(@NonNull final FilterGroup filterGroup) {
|
||||||
|
makeAllowedMenuItemInGroupCheckable(filterGroup.isOnlyOneCheckable(),
|
||||||
|
getLastUsedGroupIdThanIncrement());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
firstSortFilterGroupId = newLastUsedGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filtersVisible(final boolean areFiltersVisible) {
|
||||||
|
// no implementation here as there is no 'sort filter' title as MenuItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CreateSortFilterUI extends CreateContentFilterUI {
|
||||||
|
|
||||||
|
private void addSortFilterUiToItemMap(final int id,
|
||||||
|
final MenuItem item) {
|
||||||
|
logic.addSortFilterUiWrapperToItemMap(id, new UiItemWrapper(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
firstSortFilterGroupId = newLastUsedGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createFilterGroupBeforeItems(
|
||||||
|
@NonNull final FilterGroup filterGroup) {
|
||||||
|
if (filterGroup.getNameId() != null) {
|
||||||
|
final MenuItem item = createNotEnabledAndUncheckableGroupTitleMenuItem(
|
||||||
|
filterGroup.getIdentifier(), filterGroup.getNameId());
|
||||||
|
addSortFilterUiToItemMap(filterGroup.getIdentifier(), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createFilterItem(@NonNull final FilterItem filterItem,
|
||||||
|
@NonNull final FilterGroup filterGroup) {
|
||||||
|
final MenuItem item = createMenuItem(filterItem);
|
||||||
|
addSortFilterUiToItemMap(filterItem.getIdentifier(), item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
// no implementation here as we do not need to clean up anything or whatever
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user