Skip to content

Commit 05ffe27

Browse files
committed
searchfilters: 1st Ui: default dialog for search content and sort filters
1 parent 7c650f6 commit 05ffe27

8 files changed

Lines changed: 666 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.view.LayoutInflater;
6+
import android.view.View;
7+
import android.view.ViewGroup;
8+
9+
import org.schabi.newpipe.databinding.SearchFilterDialogFragmentBinding;
10+
11+
import androidx.annotation.NonNull;
12+
import androidx.annotation.Nullable;
13+
import androidx.appcompat.widget.Toolbar;
14+
15+
/**
16+
* A search filter dialog that also looks like a dialog aka. 'dialog style'.
17+
*/
18+
public class SearchFilterDialogFragment extends BaseSearchFilterDialogFragment {
19+
20+
protected SearchFilterDialogFragmentBinding binding;
21+
22+
@Override
23+
protected BaseSearchFilterUiGenerator createSearchFilterDialogGenerator() {
24+
return new SearchFilterDialogGenerator(
25+
searchViewModel.getSearchFilterLogic(), binding.verticalScroll, requireContext());
26+
}
27+
28+
@Override
29+
@Nullable
30+
protected Toolbar getToolbar() {
31+
return binding.toolbarLayout.toolbar;
32+
}
33+
34+
@Override
35+
protected View getRootView(@NonNull final LayoutInflater inflater,
36+
@Nullable final ViewGroup container) {
37+
binding = SearchFilterDialogFragmentBinding
38+
.inflate(inflater, container, false);
39+
return binding.getRoot();
40+
}
41+
}
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
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.content.Context;
6+
import android.view.Gravity;
7+
import android.view.LayoutInflater;
8+
import android.view.View;
9+
import android.view.ViewGroup;
10+
import android.widget.AdapterView;
11+
import android.widget.GridLayout;
12+
import android.widget.Spinner;
13+
import android.widget.TextView;
14+
15+
import com.google.android.material.chip.Chip;
16+
import com.google.android.material.chip.ChipGroup;
17+
18+
import org.schabi.newpipe.R;
19+
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
20+
import org.schabi.newpipe.extractor.search.filter.FilterItem;
21+
import org.schabi.newpipe.util.DeviceUtils;
22+
import org.schabi.newpipe.util.ServiceHelper;
23+
24+
import java.util.List;
25+
26+
import androidx.annotation.NonNull;
27+
import androidx.annotation.Nullable;
28+
29+
public class SearchFilterDialogGenerator extends BaseSearchFilterUiDialogGenerator {
30+
private static final int CHIP_GROUP_ELEMENTS_THRESHOLD = 2;
31+
private static final int CHIP_MIN_TOUCH_TARGET_SIZE_DP = 40;
32+
protected final GridLayout globalLayout;
33+
34+
public SearchFilterDialogGenerator(
35+
@NonNull final SearchFilterLogic logic,
36+
@NonNull final ViewGroup root,
37+
@NonNull final Context context) {
38+
super(logic, context);
39+
this.globalLayout = createGridLayout();
40+
root.addView(globalLayout);
41+
}
42+
43+
@Override
44+
protected void createTitle(@NonNull final String name,
45+
@NonNull final List<View> titleViewElements) {
46+
final TextView titleView = createTitleText(name);
47+
final View separatorLine = createSeparatorLine();
48+
final View separatorLine2 = createSeparatorLine();
49+
50+
globalLayout.addView(separatorLine);
51+
globalLayout.addView(titleView);
52+
globalLayout.addView(separatorLine2);
53+
54+
titleViewElements.add(titleView);
55+
titleViewElements.add(separatorLine);
56+
titleViewElements.add(separatorLine2);
57+
}
58+
59+
@Override
60+
protected void createFilterGroup(@NonNull final FilterGroup filterGroup,
61+
@NonNull final UiWrapperMapDelegate wrapperDelegate,
62+
@NonNull final UiSelectorDelegate selectorDelegate) {
63+
final GridLayout.LayoutParams layoutParams = getLayoutParamsViews();
64+
boolean doSpanDataOverMultipleCells = false;
65+
final UiItemWrapperViews viewsWrapper = new UiItemWrapperViews(
66+
filterGroup.getIdentifier());
67+
68+
final TextView filterLabel;
69+
if (filterGroup.getNameId() != null) {
70+
filterLabel = createFilterLabel(filterGroup, layoutParams);
71+
viewsWrapper.add(filterLabel);
72+
} else {
73+
filterLabel = null;
74+
doSpanDataOverMultipleCells = true;
75+
}
76+
77+
if (filterGroup.isOnlyOneCheckable()) {
78+
if (filterLabel != null) {
79+
globalLayout.addView(filterLabel);
80+
}
81+
82+
final Spinner filterDataSpinner = new Spinner(context, Spinner.MODE_DROPDOWN);
83+
84+
final GridLayout.LayoutParams spinnerLp =
85+
clipFreeRightColumnLayoutParams(doSpanDataOverMultipleCells);
86+
setDefaultMargin(spinnerLp);
87+
filterDataSpinner.setLayoutParams(spinnerLp);
88+
setZeroPadding(filterDataSpinner);
89+
90+
createUiElementsForSingleSelectableItemsFilterGroup(
91+
filterGroup, wrapperDelegate, selectorDelegate, filterDataSpinner);
92+
93+
viewsWrapper.add(filterDataSpinner);
94+
globalLayout.addView(filterDataSpinner);
95+
96+
} else { // multiple items in FilterGroup selectable
97+
final ChipGroup chipGroup = new ChipGroup(context);
98+
doSpanDataOverMultipleCells = chooseParentViewForFilterLabelAndAdd(
99+
filterGroup, doSpanDataOverMultipleCells, filterLabel, chipGroup);
100+
101+
viewsWrapper.add(chipGroup);
102+
globalLayout.addView(chipGroup);
103+
chipGroup.setLayoutParams(
104+
clipFreeRightColumnLayoutParams(doSpanDataOverMultipleCells));
105+
chipGroup.setSingleLine(false);
106+
107+
createUiChipElementsForFilterGroupItems(
108+
filterGroup, wrapperDelegate, selectorDelegate, chipGroup);
109+
}
110+
111+
wrapperDelegate.put(filterGroup.getIdentifier(), viewsWrapper);
112+
}
113+
114+
@NonNull
115+
protected TextView createFilterLabel(@NonNull final FilterGroup filterGroup,
116+
@NonNull final GridLayout.LayoutParams layoutParams) {
117+
final TextView filterLabel;
118+
filterLabel = new TextView(context);
119+
120+
filterLabel.setId(filterGroup.getIdentifier());
121+
filterLabel.setText(
122+
ServiceHelper.getTranslatedFilterString(filterGroup.getNameId(), context));
123+
filterLabel.setGravity(Gravity.CENTER_VERTICAL);
124+
setDefaultMargin(layoutParams);
125+
setZeroPadding(filterLabel);
126+
127+
filterLabel.setLayoutParams(layoutParams);
128+
return filterLabel;
129+
}
130+
131+
private boolean chooseParentViewForFilterLabelAndAdd(
132+
@NonNull final FilterGroup filterGroup,
133+
final boolean doSpanDataOverMultipleCells,
134+
@Nullable final TextView filterLabel,
135+
@NonNull final ChipGroup possibleParentView) {
136+
137+
boolean spanOverMultipleCells = doSpanDataOverMultipleCells;
138+
if (filterLabel != null) {
139+
// If we have more than CHIP_GROUP_ELEMENTS_THRESHOLD elements to be
140+
// displayed as Chips add its filterLabel as first element to ChipGroup.
141+
// Now the ChipGroup can be spanned over all the cells to use
142+
// the space better.
143+
if (filterGroup.getFilterItems().size() > CHIP_GROUP_ELEMENTS_THRESHOLD) {
144+
possibleParentView.addView(filterLabel);
145+
spanOverMultipleCells = true;
146+
} else {
147+
globalLayout.addView(filterLabel);
148+
}
149+
}
150+
return spanOverMultipleCells;
151+
}
152+
153+
private void createUiElementsForSingleSelectableItemsFilterGroup(
154+
@NonNull final FilterGroup filterGroup,
155+
@NonNull final UiWrapperMapDelegate wrapperDelegate,
156+
@NonNull final UiSelectorDelegate selectorDelegate,
157+
@NonNull final Spinner filterDataSpinner) {
158+
filterDataSpinner.setAdapter(new SearchFilterDialogSpinnerAdapter(
159+
context, filterGroup, wrapperDelegate, filterDataSpinner));
160+
161+
final AdapterView.OnItemSelectedListener listener;
162+
listener = new AdapterView.OnItemSelectedListener() {
163+
@Override
164+
public void onItemSelected(final AdapterView<?> parent, final View view,
165+
final int position, final long id) {
166+
if (view != null) {
167+
selectorDelegate.selectFilter(view.getId());
168+
}
169+
}
170+
171+
@Override
172+
public void onNothingSelected(final AdapterView<?> parent) {
173+
// we are only interested onItemSelected() -> no implementation here
174+
}
175+
};
176+
177+
filterDataSpinner.setOnItemSelectedListener(listener);
178+
}
179+
180+
protected void createUiChipElementsForFilterGroupItems(
181+
@NonNull final FilterGroup filterGroup,
182+
@NonNull final UiWrapperMapDelegate wrapperDelegate,
183+
@NonNull final UiSelectorDelegate selectorDelegate,
184+
@NonNull final ChipGroup chipGroup) {
185+
for (final FilterItem item : filterGroup.getFilterItems()) {
186+
187+
if (item instanceof InjectFilterItem.DividerItem) {
188+
final InjectFilterItem.DividerItem dividerItem =
189+
(InjectFilterItem.DividerItem) item;
190+
191+
// For the width MATCH_PARENT is necessary as this allows the
192+
// dividerLabel to fill one row of ChipGroup exclusively
193+
final ChipGroup.LayoutParams layoutParams = new ChipGroup.LayoutParams(
194+
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
195+
final TextView dividerLabel = createDividerLabel(dividerItem, layoutParams);
196+
chipGroup.addView(dividerLabel);
197+
} else {
198+
final Chip chip = createChipView(chipGroup, item);
199+
200+
final View.OnClickListener listener;
201+
listener = view -> selectorDelegate.selectFilter(view.getId());
202+
chip.setOnClickListener(listener);
203+
204+
chipGroup.addView(chip);
205+
wrapperDelegate.put(item.getIdentifier(),
206+
new UiItemWrapperChip(item, chip, chipGroup));
207+
}
208+
}
209+
}
210+
211+
@NonNull
212+
private Chip createChipView(@NonNull final ChipGroup chipGroup,
213+
@NonNull final FilterItem item) {
214+
final Chip chip = (Chip) LayoutInflater.from(context).inflate(
215+
R.layout.chip_search_filter, chipGroup, false);
216+
chip.ensureAccessibleTouchTarget(
217+
DeviceUtils.dpToPx(CHIP_MIN_TOUCH_TARGET_SIZE_DP, context));
218+
chip.setText(ServiceHelper.getTranslatedFilterString(item.getNameId(), context));
219+
chip.setId(item.getIdentifier());
220+
chip.setCheckable(true);
221+
return chip;
222+
}
223+
224+
@NonNull
225+
private TextView createDividerLabel(
226+
@NonNull final InjectFilterItem.DividerItem dividerItem,
227+
@NonNull final ViewGroup.MarginLayoutParams layoutParams) {
228+
final TextView dividerLabel;
229+
dividerLabel = new TextView(context);
230+
dividerLabel.setEnabled(true);
231+
232+
dividerLabel.setGravity(Gravity.CENTER_VERTICAL);
233+
setDefaultMargin(layoutParams);
234+
dividerLabel.setLayoutParams(layoutParams);
235+
final String menuDividerTitle =
236+
context.getString(dividerItem.getStringResId());
237+
dividerLabel.setText(menuDividerTitle);
238+
return dividerLabel;
239+
}
240+
241+
@NonNull
242+
protected SeparatorLineView createSeparatorLine() {
243+
return createSeparatorLine(clipFreeRightColumnLayoutParams(true));
244+
}
245+
246+
@NonNull
247+
private TextView createTitleText(final String name) {
248+
final TextView title = createTitleText(name,
249+
clipFreeRightColumnLayoutParams(true));
250+
title.setGravity(Gravity.CENTER);
251+
return title;
252+
}
253+
254+
@NonNull
255+
private GridLayout createGridLayout() {
256+
final GridLayout layout = new GridLayout(context);
257+
258+
layout.setColumnCount(2);
259+
260+
final GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
261+
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
262+
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
263+
setDefaultMargin(layoutParams);
264+
layout.setLayoutParams(layoutParams);
265+
266+
return layout;
267+
}
268+
269+
@NonNull
270+
protected GridLayout.LayoutParams clipFreeRightColumnLayoutParams(final boolean doColumnSpan) {
271+
final GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
272+
// https://stackoverflow.com/questions/37744672/gridlayout-children-are-being-clipped
273+
layoutParams.width = 0;
274+
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
275+
layoutParams.setGravity(Gravity.FILL_HORIZONTAL | Gravity.CENTER_VERTICAL);
276+
setDefaultMargin(layoutParams);
277+
278+
if (doColumnSpan) {
279+
layoutParams.columnSpec = GridLayout.spec(0, 2, 1.0f);
280+
}
281+
282+
return layoutParams;
283+
}
284+
285+
@NonNull
286+
private GridLayout.LayoutParams getLayoutParamsViews() {
287+
final GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
288+
layoutParams.setGravity(Gravity.CENTER_VERTICAL);
289+
setDefaultMargin(layoutParams);
290+
return layoutParams;
291+
}
292+
293+
@NonNull
294+
protected ViewGroup.MarginLayoutParams setDefaultMargin(
295+
@NonNull final ViewGroup.MarginLayoutParams layoutParams) {
296+
layoutParams.setMargins(
297+
DeviceUtils.dpToPx(4, context),
298+
DeviceUtils.dpToPx(2, context),
299+
DeviceUtils.dpToPx(4, context),
300+
DeviceUtils.dpToPx(2, context)
301+
);
302+
return layoutParams;
303+
}
304+
305+
@NonNull
306+
protected View setZeroPadding(@NonNull final View view) {
307+
view.setPadding(0, 0, 0, 0);
308+
return view;
309+
}
310+
311+
public static class UiItemWrapperChip extends BaseUiItemWrapper {
312+
313+
@NonNull
314+
private final ChipGroup chipGroup;
315+
316+
public UiItemWrapperChip(@NonNull final FilterItem item,
317+
@NonNull final View view,
318+
@NonNull final ChipGroup chipGroup) {
319+
super(item, view);
320+
this.chipGroup = chipGroup;
321+
}
322+
323+
@Override
324+
public boolean isChecked() {
325+
return ((Chip) view).isChecked();
326+
}
327+
328+
@Override
329+
public void setChecked(final boolean checked) {
330+
((Chip) view).setChecked(checked);
331+
332+
if (checked) {
333+
chipGroup.check(view.getId());
334+
}
335+
}
336+
}
337+
}

0 commit comments

Comments
 (0)