mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-10 09:20:31 +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