Skip to content

Commit 489df0e

Browse files
committed
Update NewPipeExtractor and properly linkify comments
1 parent 2db2918 commit 489df0e

8 files changed

Lines changed: 277 additions & 216 deletions

File tree

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ dependencies {
187187
// name and the commit hash with the commit hash of the (pushed) commit you want to test
188188
// This works thanks to JitPack: https://jitpack.io/
189189
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
190-
implementation 'com.github.TeamNewPipe:NewPipeExtractor:2211a24b6934a8a8cdf5547ea1b52daa4cb5de6c'
190+
implementation 'com.github.TeamNewPipe:NewPipeExtractor:ff94e9f30bc5d7831734cc85ecebe7d30ac9c040'
191191
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
192192

193193
/** Checkstyle **/

app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
55
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
66
import static org.schabi.newpipe.util.Localization.getAppLocale;
7+
import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
78

89
import android.os.Bundle;
910
import android.view.LayoutInflater;
@@ -112,7 +113,10 @@ private void enableDescriptionSelection() {
112113

113114
private void disableDescriptionSelection() {
114115
// show description content again, otherwise some links are not clickable
115-
loadDescriptionContent();
116+
TextLinkifier.fromDescription(binding.detailDescriptionView,
117+
streamInfo.getDescription(), HtmlCompat.FROM_HTML_MODE_LEGACY,
118+
streamInfo.getService(), streamInfo.getUrl(),
119+
descriptionDisposables, SET_LINK_MOVEMENT_METHOD);
116120

117121
binding.detailDescriptionNoteView.setVisibility(View.GONE);
118122
binding.detailDescriptionView.setTextIsSelectable(false);
@@ -123,27 +127,6 @@ private void disableDescriptionSelection() {
123127
binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_select_all);
124128
}
125129

126-
private void loadDescriptionContent() {
127-
final Description description = streamInfo.getDescription();
128-
switch (description.getType()) {
129-
case Description.HTML:
130-
TextLinkifier.createLinksFromHtmlBlock(binding.detailDescriptionView,
131-
description.getContent(), HtmlCompat.FROM_HTML_MODE_LEGACY, streamInfo,
132-
descriptionDisposables);
133-
break;
134-
case Description.MARKDOWN:
135-
TextLinkifier.createLinksFromMarkdownText(binding.detailDescriptionView,
136-
description.getContent(), streamInfo, descriptionDisposables);
137-
break;
138-
case Description.PLAIN_TEXT:
139-
default:
140-
TextLinkifier.createLinksFromPlainText(binding.detailDescriptionView,
141-
description.getContent(), streamInfo, descriptionDisposables);
142-
break;
143-
}
144-
}
145-
146-
147130
private void setupMetadata(final LayoutInflater inflater,
148131
final LinearLayout layout) {
149132
addMetadataItem(inflater, layout, false, R.string.metadata_category,
@@ -193,8 +176,8 @@ private void addMetadataItem(final LayoutInflater inflater,
193176
});
194177

195178
if (linkifyContent) {
196-
TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content,
197-
null, descriptionDisposables);
179+
TextLinkifier.fromPlainText(itemBinding.metadataContentView, content, null, null,
180+
descriptionDisposables, SET_LINK_MOVEMENT_METHOD);
198181
} else {
199182
itemBinding.metadataContentView.setText(content);
200183
}

app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java

Lines changed: 70 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,64 @@
11
package org.schabi.newpipe.info_list.holder;
22

3+
import android.graphics.Paint;
4+
import android.text.Layout;
35
import android.text.TextUtils;
46
import android.text.method.LinkMovementMethod;
57
import android.text.style.URLSpan;
6-
import android.text.util.Linkify;
78
import android.util.Log;
89
import android.view.View;
910
import android.view.ViewGroup;
1011
import android.widget.ImageView;
1112
import android.widget.RelativeLayout;
1213
import android.widget.TextView;
1314

15+
import androidx.annotation.Nullable;
1416
import androidx.appcompat.app.AppCompatActivity;
15-
import androidx.core.text.util.LinkifyCompat;
17+
import androidx.core.text.HtmlCompat;
1618

1719
import org.schabi.newpipe.R;
1820
import org.schabi.newpipe.error.ErrorUtil;
1921
import org.schabi.newpipe.extractor.InfoItem;
22+
import org.schabi.newpipe.extractor.NewPipe;
23+
import org.schabi.newpipe.extractor.ServiceList;
24+
import org.schabi.newpipe.extractor.StreamingService;
2025
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
26+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
27+
import org.schabi.newpipe.extractor.stream.Description;
2128
import org.schabi.newpipe.info_list.InfoItemBuilder;
2229
import org.schabi.newpipe.local.history.HistoryRecordManager;
23-
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
2430
import org.schabi.newpipe.util.DeviceUtils;
2531
import org.schabi.newpipe.util.Localization;
2632
import org.schabi.newpipe.util.NavigationHelper;
2733
import org.schabi.newpipe.util.PicassoHelper;
2834
import org.schabi.newpipe.util.external_communication.ShareUtils;
29-
import org.schabi.newpipe.util.text.TimestampExtractor;
35+
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
36+
import org.schabi.newpipe.util.text.TextLinkifier;
3037

31-
import java.util.Objects;
38+
import java.util.function.Consumer;
39+
40+
import io.reactivex.rxjava3.disposables.CompositeDisposable;
3241

3342
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
3443
private static final String TAG = "CommentsMiniIIHolder";
44+
private static final String ELLIPSIS = "…";
3545

3646
private static final int COMMENT_DEFAULT_LINES = 2;
3747
private static final int COMMENT_EXPANDED_LINES = 1000;
3848

3949
private final int commentHorizontalPadding;
4050
private final int commentVerticalPadding;
51+
private final float ellipsisWidthPx;
4152

4253
private final RelativeLayout itemRoot;
4354
private final ImageView itemThumbnailView;
4455
private final TextView itemContentView;
4556
private final TextView itemLikesCountView;
4657
private final TextView itemPublishedTime;
4758

48-
private String commentText;
59+
private final CompositeDisposable disposables = new CompositeDisposable();
60+
private Description commentText;
61+
private StreamingService streamService;
4962
private String streamUrl;
5063

5164
CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
@@ -62,6 +75,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
6275
.getResources().getDimension(R.dimen.comments_horizontal_padding);
6376
commentVerticalPadding = (int) infoItemBuilder.getContext()
6477
.getResources().getDimension(R.dimen.comments_vertical_padding);
78+
79+
final Paint paint = new Paint();
80+
paint.setTextSize(itemContentView.getTextSize());
81+
ellipsisWidthPx = paint.measureText(ELLIPSIS);
6582
}
6683

6784
public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
@@ -91,18 +108,20 @@ public void updateFromItem(final InfoItem infoItem,
91108

92109
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
93110

111+
try {
112+
streamService = NewPipe.getService(item.getServiceId());
113+
} catch (final ExtractionException e) {
114+
// should never happen
115+
ErrorUtil.showUiErrorSnackbar(itemBuilder.getContext(), "Getting StreamingService", e);
116+
Log.w(TAG, "Cannot obtain service from comment service id, defaulting to YouTube", e);
117+
streamService = ServiceList.YouTube;
118+
}
94119
streamUrl = item.getUrl();
95-
96-
itemContentView.setLines(COMMENT_DEFAULT_LINES);
97120
commentText = item.getCommentText();
98-
itemContentView.setText(commentText, TextView.BufferType.SPANNABLE);
99-
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
121+
ellipsize();
100122

101-
if (itemContentView.getLineCount() == 0) {
102-
itemContentView.post(this::ellipsize);
103-
} else {
104-
ellipsize();
105-
}
123+
//noinspection ClickableViewAccessibility
124+
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
106125

107126
if (item.getLikeCount() >= 0) {
108127
itemLikesCountView.setText(
@@ -132,7 +151,8 @@ public void updateFromItem(final InfoItem infoItem,
132151
if (DeviceUtils.isTv(itemBuilder.getContext())) {
133152
openCommentAuthor(item);
134153
} else {
135-
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
154+
ShareUtils.copyToClipboard(itemBuilder.getContext(),
155+
itemContentView.getText().toString());
136156
}
137157
return true;
138158
});
@@ -172,7 +192,7 @@ private boolean shouldFocusLinks() {
172192
return urls != null && urls.length != 0;
173193
}
174194

175-
private void determineLinkFocus() {
195+
private void determineMovementMethod() {
176196
if (shouldFocusLinks()) {
177197
allowLinkFocus();
178198
} else {
@@ -181,63 +201,51 @@ private void determineLinkFocus() {
181201
}
182202

183203
private void ellipsize() {
184-
boolean hasEllipsis = false;
185-
186-
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
187-
final int endOfLastLine = itemContentView
188-
.getLayout()
189-
.getLineEnd(COMMENT_DEFAULT_LINES - 1);
190-
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
191-
if (end == -1) {
192-
end = Math.max(endOfLastLine - 2, 0);
193-
}
194-
final String newVal = itemContentView.getText().subSequence(0, end) + " …";
195-
itemContentView.setText(newVal);
196-
hasEllipsis = true;
197-
}
204+
linkifyCommentContentView(v -> {
205+
boolean hasEllipsis = false;
198206

199-
linkify();
207+
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
208+
final int endOfLastLine = itemContentView
209+
.getLayout()
210+
.getLineEnd(COMMENT_DEFAULT_LINES - 1);
211+
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
212+
if (end == -1) {
213+
end = Math.max(endOfLastLine - 2, 0);
214+
}
215+
final String newVal = itemContentView.getText().subSequence(0, end) + " …";
216+
itemContentView.setText(newVal);
217+
hasEllipsis = true;
218+
}
200219

201-
if (hasEllipsis) {
202-
denyLinkFocus();
203-
} else {
204-
determineLinkFocus();
205-
}
220+
itemContentView.setMaxLines(COMMENT_DEFAULT_LINES);
221+
if (hasEllipsis) {
222+
denyLinkFocus();
223+
} else {
224+
determineMovementMethod();
225+
}
226+
});
206227
}
207228

208229
private void toggleEllipsize() {
209-
if (itemContentView.getText().toString().equals(commentText)) {
210-
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
211-
ellipsize();
212-
}
213-
} else {
230+
final CharSequence text = itemContentView.getText();
231+
if (text.charAt(text.length() - 1) == ELLIPSIS.charAt(0)) {
214232
expand();
233+
} else if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
234+
ellipsize();
215235
}
216236
}
217237

218238
private void expand() {
219239
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
220-
itemContentView.setText(commentText);
221-
linkify();
222-
determineLinkFocus();
240+
linkifyCommentContentView(v -> determineMovementMethod());
223241
}
224242

225-
private void linkify() {
226-
LinkifyCompat.addLinks(itemContentView, Linkify.WEB_URLS);
227-
LinkifyCompat.addLinks(itemContentView, TimestampExtractor.TIMESTAMPS_PATTERN, null, null,
228-
(match, url) -> {
229-
try {
230-
final var timestampMatch = TimestampExtractor
231-
.getTimestampFromMatcher(match, commentText);
232-
if (timestampMatch == null) {
233-
return url;
234-
}
235-
return streamUrl + url.replace(Objects.requireNonNull(match.group(0)),
236-
"#timestamp=" + timestampMatch.seconds());
237-
} catch (final Exception ex) {
238-
Log.e(TAG, "Unable to process url='" + url + "' as timestampLink", ex);
239-
return url;
240-
}
241-
});
243+
private void linkifyCommentContentView(@Nullable final Consumer<TextView> onCompletion) {
244+
disposables.clear();
245+
if (commentText != null) {
246+
TextLinkifier.fromDescription(itemContentView, commentText,
247+
HtmlCompat.FROM_HTML_MODE_LEGACY, streamService, streamUrl, disposables,
248+
onCompletion);
249+
}
242250
}
243251
}

app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.schabi.newpipe.util;
2121

2222
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
23+
import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
2324

2425
import android.content.Context;
2526
import android.util.Log;
@@ -319,8 +320,9 @@ public static void showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInf
319320
}
320321

321322
metaInfoSeparator.setVisibility(View.VISIBLE);
322-
TextLinkifier.createLinksFromHtmlBlock(metaInfoTextView, stringBuilder.toString(),
323-
HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, null, disposables);
323+
TextLinkifier.fromHtml(metaInfoTextView, stringBuilder.toString(),
324+
HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, null, null, disposables,
325+
SET_LINK_MOVEMENT_METHOD);
324326
}
325327
}
326328

app/src/main/java/org/schabi/newpipe/util/text/CommentTextOnTouchListener.java

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,37 @@
22

33
import static org.schabi.newpipe.util.text.TouchUtils.getOffsetForHorizontalLine;
44

5-
import android.text.Selection;
6-
import android.text.Spannable;
5+
import android.annotation.SuppressLint;
76
import android.text.Spanned;
87
import android.text.style.ClickableSpan;
9-
import android.text.style.URLSpan;
108
import android.view.MotionEvent;
119
import android.view.View;
1210
import android.widget.TextView;
1311

14-
import org.schabi.newpipe.util.external_communication.ShareUtils;
15-
16-
import io.reactivex.rxjava3.disposables.CompositeDisposable;
17-
1812
public class CommentTextOnTouchListener implements View.OnTouchListener {
1913
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
2014

15+
@SuppressLint("ClickableViewAccessibility")
2116
@Override
2217
public boolean onTouch(final View v, final MotionEvent event) {
2318
if (!(v instanceof TextView)) {
2419
return false;
2520
}
2621
final TextView widget = (TextView) v;
27-
final Object text = widget.getText();
22+
final CharSequence text = widget.getText();
2823
if (text instanceof Spanned) {
29-
final Spannable buffer = (Spannable) text;
30-
24+
final Spanned buffer = (Spanned) text;
3125
final int action = event.getAction();
3226

3327
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
3428
final int offset = getOffsetForHorizontalLine(widget, event);
35-
final ClickableSpan[] link = buffer.getSpans(offset, offset, ClickableSpan.class);
29+
final ClickableSpan[] links = buffer.getSpans(offset, offset, ClickableSpan.class);
3630

37-
if (link.length != 0) {
31+
if (links.length != 0) {
3832
if (action == MotionEvent.ACTION_UP) {
39-
if (link[0] instanceof URLSpan) {
40-
final String url = ((URLSpan) link[0]).getURL();
41-
if (!InternalUrlsHandler.handleUrlCommentsTimestamp(
42-
new CompositeDisposable(), v.getContext(), url)) {
43-
ShareUtils.openUrlInBrowser(v.getContext(), url, false);
44-
}
45-
}
46-
} else if (action == MotionEvent.ACTION_DOWN) {
47-
Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
48-
buffer.getSpanEnd(link[0]));
33+
links[0].onClick(widget);
4934
}
35+
// we handle events that intersect links, so return true
5036
return true;
5137
}
5238
}

0 commit comments

Comments
 (0)