Skip to content

Commit 7c650f6

Browse files
committed
searchfilters: Moving DividerItem from NewPipeExtractor into NewPipe
DividerItem was inserted in the content filter framework in the NewPipeExtractor to have a section title for YoutubeMusic. But as UI releated stuff seems a bit out of place in the Extractor I came up with injecting the DividerItem aka section title in the frontend without having to change too much in the frontend.
1 parent 3c038aa commit 7c650f6

2 files changed

Lines changed: 232 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package org.schabi.newpipe.fragments.list.search.filter;
2+
3+
import org.schabi.newpipe.App;
4+
import org.schabi.newpipe.R;
5+
import org.schabi.newpipe.extractor.NewPipe;
6+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
7+
import org.schabi.newpipe.extractor.search.filter.FilterContainer;
8+
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
9+
import org.schabi.newpipe.extractor.search.filter.FilterItem;
10+
import org.schabi.newpipe.extractor.search.filter.LibraryStringIds;
11+
import org.schabi.newpipe.extractor.services.youtube.search.filter.YoutubeFilters;
12+
13+
import java.util.List;
14+
15+
import androidx.annotation.NonNull;
16+
17+
/**
18+
* Inject a {@link FilterItem} that actually should not be a real filter.
19+
* <p>
20+
* This base class is meant to inject eg {@link DividerItem} (that inherits {@link FilterItem})
21+
* as Divider between {@link FilterItem}. It will be shown in the UI's.
22+
* <p>
23+
* Of course you have to handle {@link DividerItem} or whatever in the Ui's.
24+
* For that for example have a look at {@link SearchFilterDialogSpinnerAdapter}.
25+
*/
26+
public abstract class InjectFilterItem {
27+
28+
protected InjectFilterItem(
29+
@NonNull final String serviceName,
30+
final int injectedAfterFilterWithId,
31+
@NonNull final FilterItem toBeInjectedFilterItem) {
32+
33+
prepareAndInject(serviceName, injectedAfterFilterWithId, toBeInjectedFilterItem);
34+
}
35+
36+
// Please refer a static boolean to determine if already injected
37+
protected abstract boolean isAlreadyInjected();
38+
39+
// Please refer a static boolean to determine if already injected
40+
protected abstract void setAsInjected();
41+
42+
private void prepareAndInject(
43+
@NonNull final String serviceName,
44+
final int injectedAfterFilterWithId,
45+
@NonNull final FilterItem toBeInjectedFilterItem) {
46+
47+
if (isAlreadyInjected()) { // already run
48+
return;
49+
}
50+
51+
try { // using serviceName to test if we are trying to inject into the right service
52+
final List<FilterGroup> groups = NewPipe.getService(serviceName)
53+
.getSearchQHFactory().getAvailableContentFilter().getFilterGroups();
54+
injectFilterItemIntoGroup(
55+
groups,
56+
injectedAfterFilterWithId,
57+
toBeInjectedFilterItem);
58+
setAsInjected();
59+
} catch (final ExtractionException ignored) {
60+
// no the service we want to prepareAndInject -> so ignore
61+
}
62+
}
63+
64+
private void injectFilterItemIntoGroup(
65+
@NonNull final List<FilterGroup> groups,
66+
final int injectedAfterFilterWithId,
67+
@NonNull final FilterItem toBeInjectedFilterItem) {
68+
69+
int indexForFilterId = 0;
70+
boolean isFilterItemFound = false;
71+
FilterGroup groupWithTheSearchFilterItem = null;
72+
73+
for (final FilterGroup group : groups) {
74+
for (final FilterItem item : group.getFilterItems()) {
75+
if (item.getIdentifier() == injectedAfterFilterWithId) {
76+
isFilterItemFound = true;
77+
break;
78+
}
79+
indexForFilterId++;
80+
}
81+
82+
if (isFilterItemFound) {
83+
groupWithTheSearchFilterItem = group;
84+
break;
85+
}
86+
}
87+
88+
if (isFilterItemFound) {
89+
// we want to insert after the FilterItem we've searched
90+
indexForFilterId++;
91+
groupWithTheSearchFilterItem.getFilterItems()
92+
.add(indexForFilterId, toBeInjectedFilterItem);
93+
}
94+
}
95+
96+
/**
97+
* Inject DividerItem between YouTube content filters and YoutubeMusic content filters.
98+
*/
99+
public static class DividerBetweenYoutubeAndYoutubeMusic extends InjectFilterItem {
100+
101+
private static boolean isYoutubeMusicDividerInjected = false;
102+
103+
protected DividerBetweenYoutubeAndYoutubeMusic() {
104+
super(App.getApp().getApplicationContext().getString(R.string.youtube),
105+
YoutubeFilters.ID_CF_MAIN_PLAYLISTS,
106+
new DividerItem(R.string.search_filters_youtube_music)
107+
);
108+
}
109+
110+
/**
111+
* Have a static runner method to avoid creating unnecessary objects if already inserted.
112+
*/
113+
public static void run() {
114+
if (!isYoutubeMusicDividerInjected) {
115+
new DividerBetweenYoutubeAndYoutubeMusic();
116+
}
117+
}
118+
119+
@Override
120+
protected boolean isAlreadyInjected() {
121+
return isYoutubeMusicDividerInjected;
122+
}
123+
124+
@Override
125+
protected void setAsInjected() {
126+
isYoutubeMusicDividerInjected = true;
127+
}
128+
}
129+
130+
/**
131+
* Used to have a title divider between regular {@link FilterItem}s.
132+
*/
133+
public static class DividerItem extends FilterItem {
134+
135+
private final int resId;
136+
137+
public DividerItem(final int resId) {
138+
// the LibraryStringIds.. is not needed at all I just need one to satisfy FilterItem.
139+
super(FilterContainer.ITEM_IDENTIFIER_UNKNOWN, LibraryStringIds.SEARCH_FILTERS_ALL);
140+
this.resId = resId;
141+
}
142+
143+
public int getStringResId() {
144+
return this.resId;
145+
}
146+
}
147+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.schabi.newpipe.filter;
2+
3+
import org.junit.Test;
4+
import org.schabi.newpipe.extractor.NewPipe;
5+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
6+
import org.schabi.newpipe.extractor.search.filter.FilterContainer;
7+
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
8+
import org.schabi.newpipe.extractor.search.filter.FilterItem;
9+
import org.schabi.newpipe.extractor.services.youtube.search.filter.YoutubeFilters;
10+
import org.schabi.newpipe.fragments.list.search.filter.InjectFilterItem;
11+
12+
import java.util.Collection;
13+
import java.util.Optional;
14+
import java.util.concurrent.atomic.AtomicInteger;
15+
16+
import androidx.annotation.NonNull;
17+
18+
import static junit.framework.TestCase.assertFalse;
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertTrue;
21+
22+
public class InjectFilterItemTest {
23+
24+
static final String SERVICE_NAME = "YouTube";
25+
26+
@Test
27+
public void injectIntoFilterGroupTest() throws ExtractionException {
28+
final FilterContainer filterContainer = NewPipe.getService(SERVICE_NAME)
29+
.getSearchQHFactory().getAvailableContentFilter();
30+
31+
final AtomicInteger itemCount = new AtomicInteger();
32+
assertFalse(getInjectedFilterItem(filterContainer, itemCount).isPresent());
33+
34+
InjectDividerTestClass.run(SERVICE_NAME);
35+
36+
final int expectedInjectedItemPosition = 5;
37+
final AtomicInteger injectedItemPosition = new AtomicInteger();
38+
assertTrue(getInjectedFilterItem(filterContainer, injectedItemPosition).isPresent());
39+
assertTrue(itemCount.get() > injectedItemPosition.get());
40+
assertEquals(expectedInjectedItemPosition, injectedItemPosition.get());
41+
}
42+
43+
@NonNull
44+
private Optional<FilterItem> getInjectedFilterItem(
45+
@NonNull final FilterContainer filterContainer,
46+
@NonNull final AtomicInteger itemCount) {
47+
48+
return filterContainer.getFilterGroups().stream()
49+
.map(FilterGroup::getFilterItems)
50+
.flatMap(Collection::stream)
51+
.filter(item -> {
52+
itemCount.getAndIncrement();
53+
return item instanceof InjectFilterItem.DividerItem;
54+
})
55+
.findAny();
56+
}
57+
58+
public static class InjectDividerTestClass extends InjectFilterItem {
59+
60+
private static boolean isDividerInjected = false;
61+
62+
protected InjectDividerTestClass(@NonNull final String serviceName) {
63+
super(serviceName,
64+
YoutubeFilters.ID_CF_MAIN_PLAYLISTS,
65+
new DividerItem(0)
66+
);
67+
}
68+
69+
public static void run(final String serviceName) {
70+
if (!isDividerInjected) {
71+
new InjectDividerTestClass(serviceName);
72+
}
73+
}
74+
75+
@Override
76+
protected boolean isAlreadyInjected() {
77+
return isDividerInjected;
78+
}
79+
80+
@Override
81+
protected void setAsInjected() {
82+
isDividerInjected = true;
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)