Skip to content

Commit 9d9f7c4

Browse files
committed
searchfilters: common base classes for DialogFragment based UI's
1 parent 5b0f4eb commit 9d9f7c4

6 files changed

Lines changed: 479 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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.View;
7+
8+
import org.schabi.newpipe.R;
9+
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
10+
import org.schabi.newpipe.extractor.search.filter.FilterItem;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
/**
16+
* Common base for the {@link SearchFilterDialogGenerator} and
17+
* {@link SearchFilterOptionMenuAlikeDialogGenerator}'s
18+
* {@link SearchFilterLogic.ICreateUiForFiltersWorker} implementation.
19+
*/
20+
public abstract class BaseCreateSearchFilterUI
21+
implements SearchFilterLogic.ICreateUiForFiltersWorker {
22+
23+
protected final BaseSearchFilterUiDialogGenerator dialogGenBase;
24+
protected final Context context;
25+
protected final List<View> titleViewElements = new ArrayList<>();
26+
protected int titleResId;
27+
28+
protected BaseCreateSearchFilterUI(final BaseSearchFilterUiDialogGenerator dialogGenBase,
29+
final Context context,
30+
final int titleResId) {
31+
this.dialogGenBase = dialogGenBase;
32+
this.context = context;
33+
this.titleResId = titleResId;
34+
}
35+
36+
@Override
37+
public void createFilterItem(final FilterItem filterItem, final FilterGroup filterGroup) {
38+
// no implementation here all creation stuff is done in createFilterGroupBeforeItems
39+
}
40+
41+
@Override
42+
public void createFilterGroupAfterItems(final FilterGroup filterGroup) {
43+
// no implementation here all creation stuff is done in createFilterGroupBeforeItems
44+
}
45+
46+
@Override
47+
public void finish() {
48+
// no implementation here all creation stuff is done in createFilterGroupBeforeItems
49+
}
50+
51+
/**
52+
* This method is used to control the visibility of the title 'sort filter' if the
53+
* chosen content filter has no sort filters.
54+
*
55+
* @param areFiltersVisible true if filter visible
56+
*/
57+
@Override
58+
public void filtersVisible(final boolean areFiltersVisible) {
59+
final int visibility = areFiltersVisible ? View.VISIBLE : View.GONE;
60+
for (final View view : titleViewElements) {
61+
if (view != null) {
62+
view.setVisibility(visibility);
63+
}
64+
}
65+
}
66+
67+
public static class CreateContentFilterUI extends CreateSortFilterUI {
68+
69+
public CreateContentFilterUI(final BaseSearchFilterUiDialogGenerator dialogGenBase,
70+
final Context context) {
71+
super(dialogGenBase, context);
72+
this.titleResId = R.string.filter_search_content_filters;
73+
}
74+
75+
@Override
76+
public void createFilterGroupBeforeItems(
77+
final FilterGroup filterGroup) {
78+
dialogGenBase.createFilterGroup(filterGroup,
79+
dialogGenBase::addContentFilterUiWrapperToItemMap,
80+
dialogGenBase::selectContentFilter);
81+
}
82+
83+
@Override
84+
public void filtersVisible(final boolean areFiltersVisible) {
85+
// no implementation here. As content filters have to be always visible
86+
}
87+
}
88+
89+
public static class CreateSortFilterUI extends BaseCreateSearchFilterUI {
90+
91+
public CreateSortFilterUI(final BaseSearchFilterUiDialogGenerator dialogGenBase,
92+
final Context context) {
93+
super(dialogGenBase, context, R.string.filter_search_sort_filters);
94+
}
95+
96+
@Override
97+
public void prepare() {
98+
dialogGenBase.createTitle(context.getString(titleResId), titleViewElements);
99+
}
100+
101+
@Override
102+
public void createFilterGroupBeforeItems(final FilterGroup filterGroup) {
103+
dialogGenBase.createFilterGroup(filterGroup,
104+
dialogGenBase::addSortFilterUiWrapperToItemMap,
105+
dialogGenBase::selectSortFilter);
106+
}
107+
}
108+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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 org.schabi.newpipe.extractor.search.filter.FilterItem;
6+
7+
public abstract class BaseItemWrapper implements SearchFilterLogic.IUiItemWrapper {
8+
9+
protected final FilterItem item;
10+
11+
protected BaseItemWrapper(final FilterItem item) {
12+
this.item = item;
13+
}
14+
15+
@Override
16+
public int getItemId() {
17+
return item.getIdentifier();
18+
}
19+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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.os.Bundle;
6+
import android.view.LayoutInflater;
7+
import android.view.View;
8+
import android.view.ViewGroup;
9+
10+
import org.schabi.newpipe.R;
11+
import org.schabi.newpipe.extractor.NewPipe;
12+
import org.schabi.newpipe.extractor.StreamingService;
13+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
14+
import org.schabi.newpipe.extractor.search.filter.FilterItem;
15+
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
19+
import androidx.annotation.NonNull;
20+
import androidx.appcompat.widget.Toolbar;
21+
import androidx.fragment.app.DialogFragment;
22+
import icepick.Icepick;
23+
import icepick.State;
24+
25+
/**
26+
* Base dialog class for {@link DialogFragment} based search filter dialogs.
27+
*/
28+
public abstract class BaseSearchFilterDialogFragment extends DialogFragment {
29+
30+
private static final String CONTENT_FILTERS = "CONTENT_FILTERS";
31+
private static final String SORT_FILTERS = "SORT_FILTERS";
32+
private static final String SERVICE_ID = "SERVICE_ID";
33+
34+
@State
35+
public ArrayList<Integer> userSelectedContentFilterList;
36+
protected List<FilterItem> selectedContentFilters;
37+
protected List<FilterItem> selectedSortFilters;
38+
protected BaseSearchFilterUiGenerator dialogGenerator;
39+
@State
40+
ArrayList<Integer> userSelectedSortFilterList = null;
41+
42+
protected static DialogFragment initDialogArguments(
43+
final DialogFragment dialogFragment,
44+
final int serviceId,
45+
final ArrayList<Integer> userSelectedContentFilter,
46+
final ArrayList<Integer> userSelectedSortFilter) {
47+
final Bundle bundle = new Bundle(1);
48+
bundle.putInt(SERVICE_ID, serviceId);
49+
bundle.putIntegerArrayList(CONTENT_FILTERS, userSelectedContentFilter);
50+
bundle.putIntegerArrayList(SORT_FILTERS, userSelectedSortFilter);
51+
dialogFragment.setArguments(bundle);
52+
53+
return dialogFragment;
54+
}
55+
56+
private void initializeFilterData() {
57+
58+
assert getArguments() != null;
59+
final int serviceId = getArguments().getInt(SERVICE_ID);
60+
final ArrayList<Integer> contentFilters =
61+
getArguments().getIntegerArrayList(CONTENT_FILTERS);
62+
final ArrayList<Integer> sortFilters =
63+
getArguments().getIntegerArrayList(SORT_FILTERS);
64+
65+
final StreamingService service;
66+
try {
67+
service = NewPipe.getService(serviceId);
68+
} catch (final ExtractionException e) {
69+
throw new RuntimeException(e);
70+
}
71+
72+
dialogGenerator = createSearchFilterDialogGenerator(service,
73+
(userSelectedContentFilter, userSelectedSortFilter) -> {
74+
selectedContentFilters = userSelectedContentFilter;
75+
selectedSortFilters = userSelectedSortFilter;
76+
sendDataToParentFragment();
77+
});
78+
79+
userSelectedContentFilterList = contentFilters;
80+
userSelectedSortFilterList = sortFilters;
81+
82+
dialogGenerator.restorePreviouslySelectedFilters(
83+
userSelectedContentFilterList,
84+
userSelectedSortFilterList);
85+
86+
dialogGenerator.createSearchUI();
87+
}
88+
89+
protected abstract BaseSearchFilterUiGenerator createSearchFilterDialogGenerator(
90+
StreamingService service,
91+
SearchFilterLogic.Callback callback);
92+
93+
/**
94+
* As we have different bindings we need to get this sorted in a method.
95+
*
96+
* @return the {@link Toolbar}
97+
*/
98+
protected abstract Toolbar getToolbar();
99+
100+
protected abstract View getRootView(LayoutInflater inflater,
101+
ViewGroup container);
102+
103+
@Override
104+
public View onCreateView(@NonNull final LayoutInflater inflater,
105+
final ViewGroup container,
106+
final Bundle savedInstanceState) {
107+
final View rootView = getRootView(inflater, container);
108+
initializeFilterData();
109+
return rootView;
110+
}
111+
112+
113+
@Override
114+
public void onResume() {
115+
super.onResume();
116+
dialogGenerator.onResume();
117+
}
118+
119+
@Override
120+
public void onStop() {
121+
dialogGenerator.onPause();
122+
super.onStop();
123+
}
124+
125+
@Override
126+
public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) {
127+
super.onViewCreated(view, savedInstanceState);
128+
Icepick.restoreInstanceState(this, savedInstanceState);
129+
130+
initToolbar(getToolbar());
131+
}
132+
133+
protected void initToolbar(final Toolbar toolbar) {
134+
toolbar.setTitle(R.string.filter);
135+
toolbar.setNavigationIcon(R.drawable.ic_arrow_back);
136+
toolbar.inflateMenu(R.menu.menu_search_filter_dialog_fragment);
137+
toolbar.setNavigationOnClickListener(v -> dismiss());
138+
toolbar.setNavigationContentDescription(R.string.cancel);
139+
140+
final View okButton = toolbar.findViewById(R.id.search);
141+
okButton.setEnabled(true);
142+
143+
final View resetButton = toolbar.findViewById(R.id.reset);
144+
resetButton.setEnabled(true);
145+
146+
toolbar.setOnMenuItemClickListener(item -> {
147+
if (item.getItemId() == R.id.search) {
148+
dialogGenerator.prepareForSearch();
149+
return true;
150+
} else if (item.getItemId() == R.id.reset) {
151+
dialogGenerator.reset();
152+
return true;
153+
}
154+
return false;
155+
});
156+
}
157+
158+
@Override
159+
public void onSaveInstanceState(@NonNull final Bundle outState) {
160+
super.onSaveInstanceState(outState);
161+
// get data to save its state via Icepick
162+
userSelectedContentFilterList = dialogGenerator.getSelectedContentFilters();
163+
userSelectedSortFilterList = dialogGenerator.getSelectedSortFilters();
164+
165+
Icepick.saveInstanceState(this, outState);
166+
}
167+
168+
private void sendDataToParentFragment() {
169+
final Listener listener = (Listener) getTargetFragment();
170+
if (listener != null) {
171+
listener.onFinishSearchFilterDialog(
172+
userSelectedContentFilterList, userSelectedSortFilterList,
173+
selectedContentFilters, selectedSortFilters);
174+
}
175+
dismiss();
176+
}
177+
178+
/**
179+
* Listener to be implemented by the parent Fragment so it can receive data.
180+
*/
181+
public interface Listener {
182+
183+
void onFinishSearchFilterDialog(ArrayList<Integer> userSelectedContentFilterList,
184+
ArrayList<Integer> userSelectedSortFilterList,
185+
List<FilterItem> selectedContentFilters,
186+
List<FilterItem> selectedSortFilters);
187+
}
188+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.View;
7+
import android.view.ViewGroup;
8+
import android.widget.TextView;
9+
10+
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
11+
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
12+
13+
import java.util.HashMap;
14+
import java.util.List;
15+
import java.util.Map;
16+
17+
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
18+
19+
public abstract class BaseSearchFilterUiDialogGenerator extends BaseSearchFilterUiGenerator {
20+
private static final float FONT_SIZE_TITLE_ITEMS_IN_DIP = 20f;
21+
22+
protected final Map<View, View.OnClickListener> viewListeners = new HashMap<>();
23+
24+
protected BaseSearchFilterUiDialogGenerator(
25+
final SearchQueryHandlerFactory linkHandlerFactory,
26+
final Callback callback,
27+
final Context context) {
28+
super(linkHandlerFactory, callback, context);
29+
}
30+
31+
protected abstract void createTitle(String name, List<View> titleViewElements);
32+
33+
protected abstract void createFilterGroup(FilterGroup filterGroup,
34+
UiWrapperMapDelegate wrapperDelegate,
35+
UiSelectorDelegate selectorDelegate);
36+
37+
@Override
38+
protected ICreateUiForFiltersWorker createContentFilterWorker() {
39+
return new BaseCreateSearchFilterUI.CreateContentFilterUI(this, context);
40+
}
41+
42+
@Override
43+
protected ICreateUiForFiltersWorker createSortFilterWorker() {
44+
return new BaseCreateSearchFilterUI.CreateSortFilterUI(this, context);
45+
}
46+
47+
@Override
48+
public void onResume() {
49+
for (final Map.Entry<View, View.OnClickListener> view : viewListeners.entrySet()) {
50+
view.getKey().setOnClickListener(view.getValue());
51+
}
52+
}
53+
54+
@Override
55+
public void onPause() {
56+
for (final Map.Entry<View, View.OnClickListener> view : viewListeners.entrySet()) {
57+
view.getKey().setOnClickListener(null);
58+
}
59+
}
60+
61+
protected View createSeparatorLine(final ViewGroup.LayoutParams layoutParams) {
62+
final View separatorLine = new View(context);
63+
separatorLine.setBackgroundColor(getSeparatorLineColorFromTheme());
64+
layoutParams.height = 1; // always set the separator to the height of 1
65+
separatorLine.setLayoutParams(layoutParams);
66+
return separatorLine;
67+
}
68+
69+
protected TextView createTitleText(final String name,
70+
final ViewGroup.LayoutParams layoutParams) {
71+
final TextView title = new TextView(context);
72+
title.setText(name);
73+
title.setTextSize(COMPLEX_UNIT_DIP, FONT_SIZE_TITLE_ITEMS_IN_DIP);
74+
title.setLayoutParams(layoutParams);
75+
return title;
76+
}
77+
}

0 commit comments

Comments
 (0)