-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
466ddb6
commit a0d576f
Showing
2 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
73 changes: 73 additions & 0 deletions
73
app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragmentLegacy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} | ||
} |
303 changes: 303 additions & 0 deletions
303
...c/main/java/org/schabi/newpipe/fragments/list/search/filter/SearchFilterUIOptionMenu.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
} |