Skip to content

Commit 12a78a8

Browse files
committed
Added preference search "framework"
1 parent 4a061f2 commit 12a78a8

9 files changed

Lines changed: 840 additions & 0 deletions
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package org.schabi.newpipe.settings.preferencesearch;
2+
3+
import android.content.Context;
4+
import android.text.TextUtils;
5+
import android.util.Log;
6+
import androidx.annotation.NonNull;
7+
import androidx.annotation.Nullable;
8+
import androidx.preference.PreferenceManager;
9+
10+
import org.xmlpull.v1.XmlPullParser;
11+
12+
import java.util.ArrayList;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import java.util.Map;
16+
import java.util.Objects;
17+
18+
/**
19+
* Parses the corresponding preference-file(s).
20+
*/
21+
class PreferenceParser {
22+
private static final String TAG = "PreferenceParser";
23+
24+
private static final String NS_ANDROID = "http://schemas.android.com/apk/res/android";
25+
private static final String NS_SEARCH = "http://schemas.android.com/apk/preferencesearch";
26+
27+
private final Context context;
28+
private final Map<String, ?> allPreferences;
29+
private final PreferenceSearchConfiguration searchConfiguration;
30+
31+
PreferenceParser(
32+
final Context context,
33+
final PreferenceSearchConfiguration searchConfiguration
34+
) {
35+
this.context = context;
36+
this.allPreferences = PreferenceManager.getDefaultSharedPreferences(context).getAll();
37+
this.searchConfiguration = searchConfiguration;
38+
}
39+
40+
public List<PreferenceSearchItem> parse(
41+
final PreferenceSearchConfiguration.SearchIndexItem item
42+
) {
43+
Objects.requireNonNull(item, "item can't be null");
44+
45+
final List<PreferenceSearchItem> results = new ArrayList<>();
46+
final XmlPullParser xpp = context.getResources().getXml(item.getResId());
47+
48+
try {
49+
xpp.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
50+
xpp.setFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES, true);
51+
52+
final List<String> breadcrumbs = new ArrayList<>();
53+
if (!TextUtils.isEmpty(item.getBreadcrumb())) {
54+
breadcrumbs.add(item.getBreadcrumb());
55+
}
56+
while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
57+
if (xpp.getEventType() == XmlPullParser.START_TAG) {
58+
final PreferenceSearchItem result = parseSearchResult(
59+
xpp,
60+
joinBreadcrumbs(breadcrumbs),
61+
item.getResId()
62+
);
63+
64+
if (!searchConfiguration.getParserIgnoreElements().contains(xpp.getName())
65+
&& result.hasData()
66+
&& !"true".equals(getAttribute(xpp, NS_SEARCH, "ignore"))) {
67+
results.add(result);
68+
}
69+
if (searchConfiguration.getParserContainerElements().contains(xpp.getName())) {
70+
breadcrumbs.add(result.getTitle() == null ? "" : result.getTitle());
71+
}
72+
} else if (xpp.getEventType() == XmlPullParser.END_TAG
73+
&& searchConfiguration.getParserContainerElements()
74+
.contains(xpp.getName())) {
75+
breadcrumbs.remove(breadcrumbs.size() - 1);
76+
}
77+
78+
xpp.next();
79+
}
80+
} catch (final Exception e) {
81+
Log.w(TAG, "Failed to parse resid=" + item.getResId(), e);
82+
}
83+
return results;
84+
}
85+
86+
private String joinBreadcrumbs(final List<String> breadcrumbs) {
87+
return breadcrumbs.stream()
88+
.filter(crumb -> !TextUtils.isEmpty(crumb))
89+
.reduce("", searchConfiguration.getBreadcrumbConcat());
90+
}
91+
92+
private String getAttribute(
93+
final XmlPullParser xpp,
94+
@NonNull final String attribute
95+
) {
96+
final String nsSearchAttr = getAttribute(xpp, NS_SEARCH, attribute);
97+
if (nsSearchAttr != null) {
98+
return nsSearchAttr;
99+
}
100+
return getAttribute(xpp, NS_ANDROID, attribute);
101+
}
102+
103+
private String getAttribute(
104+
final XmlPullParser xpp,
105+
@NonNull final String namespace,
106+
@NonNull final String attribute
107+
) {
108+
return xpp.getAttributeValue(namespace, attribute);
109+
}
110+
111+
private PreferenceSearchItem parseSearchResult(
112+
final XmlPullParser xpp,
113+
final String breadcrumbs,
114+
final int searchIndexItemResId
115+
) {
116+
final String key = readString(getAttribute(xpp, "key"));
117+
final String[] entries = readStringArray(getAttribute(xpp, "entries"));
118+
final String[] entryValues = readStringArray(getAttribute(xpp, "entryValues"));
119+
120+
return new PreferenceSearchItem(
121+
key,
122+
tryFillInPreferenceValue(
123+
readString(getAttribute(xpp, "title")),
124+
key,
125+
entries,
126+
entryValues),
127+
tryFillInPreferenceValue(
128+
readString(getAttribute(xpp, "summary")),
129+
key,
130+
entries,
131+
entryValues),
132+
TextUtils.join(",", entries),
133+
readString(getAttribute(xpp, NS_SEARCH, "keywords")),
134+
breadcrumbs,
135+
searchIndexItemResId
136+
);
137+
}
138+
139+
private String[] readStringArray(@Nullable final String s) {
140+
if (s == null) {
141+
return new String[0];
142+
}
143+
if (s.startsWith("@")) {
144+
try {
145+
return context.getResources().getStringArray(Integer.parseInt(s.substring(1)));
146+
} catch (final Exception e) {
147+
Log.w(TAG, "Unable to readStringArray from '" + s + "'", e);
148+
}
149+
}
150+
return new String[0];
151+
}
152+
153+
private String readString(@Nullable final String s) {
154+
if (s == null) {
155+
return "";
156+
}
157+
if (s.startsWith("@")) {
158+
try {
159+
return context.getString(Integer.parseInt(s.substring(1)));
160+
} catch (final Exception e) {
161+
Log.w(TAG, "Unable to readString from '" + s + "'", e);
162+
}
163+
}
164+
return s;
165+
}
166+
167+
private String tryFillInPreferenceValue(
168+
@Nullable final String s,
169+
@Nullable final String key,
170+
final String[] entries,
171+
final String[] entryValues
172+
) {
173+
if (s == null) {
174+
return "";
175+
}
176+
if (key == null) {
177+
return s;
178+
}
179+
180+
// Resolve value
181+
Object prefValue = allPreferences.get(key);
182+
if (prefValue == null) {
183+
return s;
184+
}
185+
186+
/*
187+
* Resolve ListPreference values
188+
*
189+
* entryValues = Values/Keys that are saved
190+
* entries = Actual human readable names
191+
*/
192+
if (entries.length > 0 && entryValues.length == entries.length) {
193+
final int entryIndex = Arrays.asList(entryValues).indexOf(prefValue);
194+
if (entryIndex != -1) {
195+
prefValue = entries[entryIndex];
196+
}
197+
}
198+
199+
return String.format(s, prefValue.toString());
200+
}
201+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.schabi.newpipe.settings.preferencesearch;
2+
3+
import android.text.TextUtils;
4+
import android.view.LayoutInflater;
5+
import android.view.View;
6+
import android.view.ViewGroup;
7+
import android.widget.TextView;
8+
9+
import androidx.annotation.NonNull;
10+
import androidx.recyclerview.widget.RecyclerView;
11+
12+
import org.schabi.newpipe.R;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.function.Consumer;
17+
18+
class PreferenceSearchAdapter
19+
extends RecyclerView.Adapter<PreferenceSearchAdapter.PreferenceViewHolder> {
20+
private List<PreferenceSearchItem> dataset = new ArrayList<>();
21+
private Consumer<PreferenceSearchItem> onItemClickListener;
22+
23+
@NonNull
24+
@Override
25+
public PreferenceSearchAdapter.PreferenceViewHolder onCreateViewHolder(
26+
@NonNull final ViewGroup parent,
27+
final int viewType
28+
) {
29+
return new PreferenceViewHolder(
30+
LayoutInflater
31+
.from(parent.getContext())
32+
.inflate(R.layout.settings_preferencesearch_list_item_result, parent, false));
33+
}
34+
35+
@Override
36+
public void onBindViewHolder(
37+
@NonNull final PreferenceSearchAdapter.PreferenceViewHolder holder,
38+
final int position
39+
) {
40+
final PreferenceSearchItem item = dataset.get(position);
41+
42+
holder.title.setText(item.getTitle());
43+
44+
if (TextUtils.isEmpty(item.getSummary())) {
45+
holder.summary.setVisibility(View.GONE);
46+
} else {
47+
holder.summary.setVisibility(View.VISIBLE);
48+
holder.summary.setText(item.getSummary());
49+
}
50+
51+
if (TextUtils.isEmpty(item.getBreadcrumbs())) {
52+
holder.breadcrumbs.setVisibility(View.GONE);
53+
} else {
54+
holder.breadcrumbs.setVisibility(View.VISIBLE);
55+
holder.breadcrumbs.setText(item.getBreadcrumbs());
56+
}
57+
58+
holder.itemView.setOnClickListener(v -> {
59+
if (onItemClickListener != null) {
60+
onItemClickListener.accept(item);
61+
}
62+
});
63+
}
64+
65+
void setContent(final List<PreferenceSearchItem> items) {
66+
dataset = new ArrayList<>(items);
67+
this.notifyDataSetChanged();
68+
}
69+
70+
@Override
71+
public int getItemCount() {
72+
return dataset.size();
73+
}
74+
75+
void setOnItemClickListener(final Consumer<PreferenceSearchItem> onItemClickListener) {
76+
this.onItemClickListener = onItemClickListener;
77+
}
78+
79+
static class PreferenceViewHolder extends RecyclerView.ViewHolder {
80+
final TextView title;
81+
final TextView summary;
82+
final TextView breadcrumbs;
83+
84+
PreferenceViewHolder(final View v) {
85+
super(v);
86+
title = v.findViewById(R.id.title);
87+
summary = v.findViewById(R.id.summary);
88+
breadcrumbs = v.findViewById(R.id.breadcrumbs);
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)