Skip to content

Commit a0d576f

Browse files
committed
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.
1 parent 466ddb6 commit a0d576f

2 files changed

Lines changed: 376 additions & 0 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.fragments.list.search;
4+
5+
import android.os.Bundle;
6+
import android.view.Menu;
7+
import android.view.MenuInflater;
8+
import android.view.MenuItem;
9+
import android.view.View;
10+
11+
import org.schabi.newpipe.R;
12+
import org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic;
13+
import org.schabi.newpipe.fragments.list.search.filter.SearchFilterUIOptionMenu;
14+
15+
import androidx.annotation.NonNull;
16+
import androidx.appcompat.widget.Toolbar;
17+
import androidx.core.content.ContextCompat;
18+
import icepick.State;
19+
20+
/**
21+
* Fragment that hosts the action menu based filter 'dialog'.
22+
* <p>
23+
* Called ..Legacy because this was the way NewPipe had implemented the search filter dialog.
24+
* <p>
25+
* The new UI's are handled by {@link SearchFragment} and implemented by
26+
* using {@link androidx.fragment.app.DialogFragment}.
27+
*/
28+
public class SearchFragmentLegacy extends SearchFragment {
29+
30+
@State
31+
protected int countOnPrepareOptionsMenuCalls = 0;
32+
private SearchFilterUIOptionMenu searchFilterUi;
33+
34+
@Override
35+
protected void initViewModel() {
36+
logicVariant = SearchFilterLogic.Factory.Variant.SEARCH_FILTER_LOGIC_LEGACY;
37+
super.initViewModel();
38+
39+
searchFilterUi = new SearchFilterUIOptionMenu(
40+
searchViewModel.getSearchFilterLogic(), requireContext());
41+
}
42+
43+
@Override
44+
protected void createMenu(@NonNull final Menu menu,
45+
@NonNull final MenuInflater inflater) {
46+
searchFilterUi.createSearchUI(menu);
47+
}
48+
49+
@Override
50+
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
51+
return searchFilterUi.onOptionsItemSelected(item);
52+
}
53+
54+
@Override
55+
protected void initViews(final View rootView,
56+
final Bundle savedInstanceState) {
57+
super.initViews(rootView, savedInstanceState);
58+
final Toolbar toolbar = (Toolbar) searchToolbarContainer.getParent();
59+
toolbar.setOverflowIcon(ContextCompat.getDrawable(requireContext(),
60+
R.drawable.ic_sort));
61+
}
62+
63+
@Override
64+
public void onPrepareOptionsMenu(@NonNull final Menu menu) {
65+
super.onPrepareOptionsMenu(menu);
66+
// workaround: we want to hide the keyboard in case we open the options
67+
// menu. As somehow this method gets triggered twice but only the 2nd
68+
// time is relevant as the options menu is selected by the user.
69+
if (++countOnPrepareOptionsMenuCalls > 1) {
70+
hideKeyboardSearch();
71+
}
72+
}
73+
}
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
2+
3+
package org.schabi.newpipe.fragments.list.search.filter;
4+
5+
import android.annotation.SuppressLint;
6+
import android.content.Context;
7+
import android.util.Log;
8+
import android.view.Menu;
9+
import android.view.MenuItem;
10+
import android.view.View;
11+
12+
import org.schabi.newpipe.R;
13+
import org.schabi.newpipe.extractor.search.filter.FilterContainer;
14+
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
15+
import org.schabi.newpipe.extractor.search.filter.FilterItem;
16+
import org.schabi.newpipe.extractor.search.filter.LibraryStringIds;
17+
import org.schabi.newpipe.util.ServiceHelper;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import androidx.annotation.NonNull;
23+
import androidx.appcompat.view.menu.MenuBuilder;
24+
import androidx.core.view.MenuCompat;
25+
26+
import static android.content.ContentValues.TAG;
27+
import static org.schabi.newpipe.fragments.list.search.filter.InjectFilterItem.DividerItem;
28+
import static org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic.ICreateUiForFiltersWorker;
29+
import static org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic.IUiItemWrapper;
30+
31+
/**
32+
* The implementation of the action menu based 'dialog'.
33+
*/
34+
public class SearchFilterUIOptionMenu extends BaseSearchFilterUiGenerator {
35+
36+
// Menu groups identifier
37+
private static final int MENU_GROUP_SEARCH_RESET_BUTTONS = 0;
38+
// give them negative ids to not conflict with the ids of the filters
39+
private static final int MENU_ID_SEARCH_BUTTON = -100;
40+
private static final int MENU_ID_RESET_BUTTON = -101;
41+
private Menu menu = null;
42+
// initialize with first group id -> next group after the search/reset buttons group
43+
private int newLastUsedGroupId = MENU_GROUP_SEARCH_RESET_BUTTONS + 1;
44+
private int firstSortFilterGroupId;
45+
46+
public SearchFilterUIOptionMenu(
47+
@NonNull final SearchFilterLogic logic,
48+
@NonNull final Context context) {
49+
super(logic, context);
50+
}
51+
52+
int getLastUsedGroupIdThanIncrement() {
53+
return newLastUsedGroupId++;
54+
}
55+
56+
@SuppressLint("RestrictedApi")
57+
private void alwaysShowMenuItemIcon(final Menu theMenu) {
58+
// always show icons
59+
if (theMenu instanceof MenuBuilder) {
60+
final MenuBuilder builder = ((MenuBuilder) theMenu);
61+
builder.setOptionalIconsVisible(true);
62+
}
63+
}
64+
65+
public void createSearchUI(@NonNull final Menu theMenu) {
66+
this.menu = theMenu;
67+
alwaysShowMenuItemIcon(theMenu);
68+
69+
createSearchUI();
70+
71+
MenuCompat.setGroupDividerEnabled(theMenu, true);
72+
}
73+
74+
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
75+
if (item.getGroupId() == MENU_GROUP_SEARCH_RESET_BUTTONS
76+
&& item.getItemId() == MENU_ID_SEARCH_BUTTON) {
77+
logic.prepareForSearch();
78+
} else { // all other menu groups -> reset, content filters and sort filters
79+
80+
// main part for holding onto the menu -> not closing it
81+
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
82+
item.setActionView(new View(context));
83+
item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
84+
85+
@Override
86+
public boolean onMenuItemActionExpand(final MenuItem item) {
87+
if (item.getGroupId() == MENU_GROUP_SEARCH_RESET_BUTTONS
88+
&& item.getItemId() == MENU_ID_RESET_BUTTON) {
89+
logic.reset();
90+
} else if (item.getGroupId() < firstSortFilterGroupId) { // content filters
91+
final int filterId = item.getItemId();
92+
logic.selectContentFilter(filterId);
93+
} else { // the sort filters
94+
Log.d(TAG, "onMenuItemActionExpand: sort filters are here");
95+
logic.selectSortFilter(item.getItemId());
96+
}
97+
98+
return false;
99+
}
100+
101+
@Override
102+
public boolean onMenuItemActionCollapse(final MenuItem item) {
103+
return false;
104+
}
105+
});
106+
}
107+
108+
return false;
109+
}
110+
111+
@Override
112+
protected ICreateUiForFiltersWorker createSortFilterWorker() {
113+
return new CreateSortFilterUI();
114+
}
115+
116+
@Override
117+
protected ICreateUiForFiltersWorker createContentFilterWorker() {
118+
return new CreateContentFilterUI();
119+
}
120+
121+
private static class UiItemWrapper implements IUiItemWrapper {
122+
123+
private final MenuItem item;
124+
125+
UiItemWrapper(final MenuItem item) {
126+
this.item = item;
127+
}
128+
129+
@Override
130+
public void setVisible(final boolean visible) {
131+
item.setVisible(visible);
132+
}
133+
134+
@Override
135+
public int getItemId() {
136+
return item.getItemId();
137+
}
138+
139+
@Override
140+
public boolean isChecked() {
141+
return item.isChecked();
142+
}
143+
144+
@Override
145+
public void setChecked(final boolean checked) {
146+
item.setChecked(checked);
147+
}
148+
}
149+
150+
private class CreateContentFilterUI implements ICreateUiForFiltersWorker {
151+
152+
/**
153+
* MenuItem's that should not be checkable.
154+
*/
155+
final List<MenuItem> nonCheckableMenuItems = new ArrayList<>();
156+
157+
/**
158+
* {@link Menu#setGroupCheckable(int, boolean, boolean)} makes all {@link MenuItem}
159+
* checkable.
160+
* <p>
161+
* We do not want a group header or a group divider to be checkable. Therefore this method
162+
* calls above mentioned method and afterwards makes all items uncheckable that are placed
163+
* inside {@link #nonCheckableMenuItems}.
164+
*
165+
* @param isOnlyOneCheckable is in group only one selection allowed.
166+
* @param groupId which group should be affected
167+
*/
168+
private void makeAllowedMenuItemInGroupCheckable(final boolean isOnlyOneCheckable,
169+
final int groupId) {
170+
// this method makes all MenuItem's checkable
171+
menu.setGroupCheckable(groupId, true, isOnlyOneCheckable);
172+
// uncheckable unwanted
173+
for (final MenuItem uncheckableItem : nonCheckableMenuItems) {
174+
if (uncheckableItem != null) {
175+
uncheckableItem.setCheckable(false);
176+
}
177+
}
178+
nonCheckableMenuItems.clear();
179+
}
180+
181+
@Override
182+
public void prepare() {
183+
// create the search button
184+
menu.add(MENU_GROUP_SEARCH_RESET_BUTTONS,
185+
MENU_ID_SEARCH_BUTTON,
186+
0,
187+
context.getString(R.string.search))
188+
.setEnabled(true)
189+
.setCheckable(false)
190+
.setIcon(R.drawable.ic_search);
191+
192+
menu.add(MENU_GROUP_SEARCH_RESET_BUTTONS,
193+
MENU_ID_RESET_BUTTON,
194+
0,
195+
context.getString(R.string.playback_reset))
196+
.setEnabled(true)
197+
.setCheckable(false)
198+
.setIcon(R.drawable.ic_settings_backup_restore);
199+
}
200+
201+
@Override
202+
public void createFilterGroupBeforeItems(
203+
@NonNull final FilterGroup filterGroup) {
204+
if (filterGroup.getNameId() != null) {
205+
createNotEnabledAndUncheckableGroupTitleMenuItem(
206+
FilterContainer.ITEM_IDENTIFIER_UNKNOWN, filterGroup.getNameId());
207+
}
208+
}
209+
210+
protected MenuItem createNotEnabledAndUncheckableGroupTitleMenuItem(
211+
final int identifier,
212+
final LibraryStringIds nameId) {
213+
final MenuItem item = menu.add(
214+
newLastUsedGroupId,
215+
identifier,
216+
0,
217+
ServiceHelper.getTranslatedFilterString(nameId, context));
218+
item.setEnabled(false);
219+
220+
nonCheckableMenuItems.add(item);
221+
222+
return item;
223+
224+
}
225+
226+
@Override
227+
public void createFilterItem(@NonNull final FilterItem filterItem,
228+
@NonNull final FilterGroup filterGroup) {
229+
final MenuItem item = createMenuItem(filterItem);
230+
231+
if (filterItem instanceof DividerItem) {
232+
final DividerItem dividerItem = (DividerItem) filterItem;
233+
final String menuDividerTitle = ">>>"
234+
+ context.getString(dividerItem.getStringResId())
235+
+ "<<<";
236+
item.setTitle(menuDividerTitle);
237+
item.setEnabled(false);
238+
nonCheckableMenuItems.add(item);
239+
}
240+
241+
logic.addContentFilterUiWrapperToItemMap(filterItem.getIdentifier(),
242+
new UiItemWrapper(item));
243+
}
244+
245+
protected MenuItem createMenuItem(final FilterItem filterItem) {
246+
return menu.add(newLastUsedGroupId,
247+
filterItem.getIdentifier(),
248+
0,
249+
ServiceHelper.getTranslatedFilterString(filterItem.getNameId(), context));
250+
}
251+
252+
@Override
253+
public void createFilterGroupAfterItems(@NonNull final FilterGroup filterGroup) {
254+
makeAllowedMenuItemInGroupCheckable(filterGroup.isOnlyOneCheckable(),
255+
getLastUsedGroupIdThanIncrement());
256+
}
257+
258+
@Override
259+
public void finish() {
260+
firstSortFilterGroupId = newLastUsedGroupId;
261+
}
262+
263+
@Override
264+
public void filtersVisible(final boolean areFiltersVisible) {
265+
// no implementation here as there is no 'sort filter' title as MenuItem
266+
}
267+
}
268+
269+
private class CreateSortFilterUI extends CreateContentFilterUI {
270+
271+
private void addSortFilterUiToItemMap(final int id,
272+
final MenuItem item) {
273+
logic.addSortFilterUiWrapperToItemMap(id, new UiItemWrapper(item));
274+
}
275+
276+
@Override
277+
public void prepare() {
278+
firstSortFilterGroupId = newLastUsedGroupId;
279+
}
280+
281+
@Override
282+
public void createFilterGroupBeforeItems(
283+
@NonNull final FilterGroup filterGroup) {
284+
if (filterGroup.getNameId() != null) {
285+
final MenuItem item = createNotEnabledAndUncheckableGroupTitleMenuItem(
286+
filterGroup.getIdentifier(), filterGroup.getNameId());
287+
addSortFilterUiToItemMap(filterGroup.getIdentifier(), item);
288+
}
289+
}
290+
291+
@Override
292+
public void createFilterItem(@NonNull final FilterItem filterItem,
293+
@NonNull final FilterGroup filterGroup) {
294+
final MenuItem item = createMenuItem(filterItem);
295+
addSortFilterUiToItemMap(filterItem.getIdentifier(), item);
296+
}
297+
298+
@Override
299+
public void finish() {
300+
// no implementation here as we do not need to clean up anything or whatever
301+
}
302+
}
303+
}

0 commit comments

Comments
 (0)