Skip to content

Commit

Permalink
Update NewPipeExtractor and properly linkify comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Stypox committed Jan 15, 2023
1 parent 2db2918 commit c038f8c
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 216 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:2211a24b6934a8a8cdf5547ea1b52daa4cb5de6c'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:ff94e9f30bc5d7831734cc85ecebe7d30ac9c040'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'

/** Checkstyle **/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.util.Localization.getAppLocale;
import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;

import android.os.Bundle;
import android.view.LayoutInflater;
Expand Down Expand Up @@ -112,7 +113,10 @@ private void enableDescriptionSelection() {

private void disableDescriptionSelection() {
// show description content again, otherwise some links are not clickable
loadDescriptionContent();
TextLinkifier.fromDescription(binding.detailDescriptionView,
streamInfo.getDescription(), HtmlCompat.FROM_HTML_MODE_LEGACY,
streamInfo.getService(), streamInfo.getUrl(),
descriptionDisposables, SET_LINK_MOVEMENT_METHOD);

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

private void loadDescriptionContent() {
final Description description = streamInfo.getDescription();
switch (description.getType()) {
case Description.HTML:
TextLinkifier.createLinksFromHtmlBlock(binding.detailDescriptionView,
description.getContent(), HtmlCompat.FROM_HTML_MODE_LEGACY, streamInfo,
descriptionDisposables);
break;
case Description.MARKDOWN:
TextLinkifier.createLinksFromMarkdownText(binding.detailDescriptionView,
description.getContent(), streamInfo, descriptionDisposables);
break;
case Description.PLAIN_TEXT:
default:
TextLinkifier.createLinksFromPlainText(binding.detailDescriptionView,
description.getContent(), streamInfo, descriptionDisposables);
break;
}
}


private void setupMetadata(final LayoutInflater inflater,
final LinearLayout layout) {
addMetadataItem(inflater, layout, false, R.string.metadata_category,
Expand Down Expand Up @@ -193,8 +176,8 @@ private void addMetadataItem(final LayoutInflater inflater,
});

if (linkifyContent) {
TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content,
null, descriptionDisposables);
TextLinkifier.fromPlainText(itemBinding.metadataContentView, content, null, null,
descriptionDisposables, SET_LINK_MOVEMENT_METHOD);
} else {
itemBinding.metadataContentView.setText(content);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,64 @@
package org.schabi.newpipe.info_list.holder;

import android.graphics.Paint;
import android.text.Layout;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.text.util.LinkifyCompat;
import androidx.core.text.HtmlCompat;

import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.text.TimestampExtractor;
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
import org.schabi.newpipe.util.text.TextLinkifier;

import java.util.Objects;
import java.util.function.Consumer;

import io.reactivex.rxjava3.disposables.CompositeDisposable;

public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private static final String TAG = "CommentsMiniIIHolder";
private static final String ELLIPSIS = "…";

private static final int COMMENT_DEFAULT_LINES = 2;
private static final int COMMENT_EXPANDED_LINES = 1000;

private final int commentHorizontalPadding;
private final int commentVerticalPadding;
private final float ellipsisWidthPx;

private final RelativeLayout itemRoot;
private final ImageView itemThumbnailView;
private final TextView itemContentView;
private final TextView itemLikesCountView;
private final TextView itemPublishedTime;

private String commentText;
private final CompositeDisposable disposables = new CompositeDisposable();
private Description commentText;
private StreamingService streamService;
private String streamUrl;

CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
Expand All @@ -62,6 +75,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
.getResources().getDimension(R.dimen.comments_horizontal_padding);
commentVerticalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_vertical_padding);

final Paint paint = new Paint();
paint.setTextSize(itemContentView.getTextSize());
ellipsisWidthPx = paint.measureText(ELLIPSIS);
}

public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
Expand Down Expand Up @@ -91,18 +108,19 @@ public void updateFromItem(final InfoItem infoItem,

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

try {
streamService = NewPipe.getService(item.getServiceId());
} catch (final ExtractionException e) {
// should never happen
Log.w(TAG, "Cannot obtain service from comment service id, defaulting to YouTube", e);
streamService = ServiceList.YouTube;
}
streamUrl = item.getUrl();

itemContentView.setLines(COMMENT_DEFAULT_LINES);
commentText = item.getCommentText();
itemContentView.setText(commentText, TextView.BufferType.SPANNABLE);
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
ellipsize();

if (itemContentView.getLineCount() == 0) {
itemContentView.post(this::ellipsize);
} else {
ellipsize();
}
//noinspection ClickableViewAccessibility
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);

if (item.getLikeCount() >= 0) {
itemLikesCountView.setText(
Expand Down Expand Up @@ -132,7 +150,8 @@ public void updateFromItem(final InfoItem infoItem,
if (DeviceUtils.isTv(itemBuilder.getContext())) {
openCommentAuthor(item);
} else {
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
ShareUtils.copyToClipboard(itemBuilder.getContext(),
itemContentView.getText().toString());
}
return true;
});
Expand Down Expand Up @@ -172,7 +191,7 @@ private boolean shouldFocusLinks() {
return urls != null && urls.length != 0;
}

private void determineLinkFocus() {
private void determineMovementMethod() {
if (shouldFocusLinks()) {
allowLinkFocus();
} else {
Expand All @@ -181,63 +200,51 @@ private void determineLinkFocus() {
}

private void ellipsize() {
boolean hasEllipsis = false;

if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
final int endOfLastLine = itemContentView
.getLayout()
.getLineEnd(COMMENT_DEFAULT_LINES - 1);
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
if (end == -1) {
end = Math.max(endOfLastLine - 2, 0);
}
final String newVal = itemContentView.getText().subSequence(0, end) + " …";
itemContentView.setText(newVal);
hasEllipsis = true;
}
linkifyCommentContentView(v -> {
boolean hasEllipsis = false;

linkify();
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
final int endOfLastLine = itemContentView
.getLayout()
.getLineEnd(COMMENT_DEFAULT_LINES - 1);
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
if (end == -1) {
end = Math.max(endOfLastLine - 2, 0);
}
final String newVal = itemContentView.getText().subSequence(0, end) + " …";
itemContentView.setText(newVal);
hasEllipsis = true;
}

if (hasEllipsis) {
denyLinkFocus();
} else {
determineLinkFocus();
}
itemContentView.setMaxLines(COMMENT_DEFAULT_LINES);
if (hasEllipsis) {
denyLinkFocus();
} else {
determineMovementMethod();
}
});
}

private void toggleEllipsize() {
if (itemContentView.getText().toString().equals(commentText)) {
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
ellipsize();
}
} else {
final CharSequence text = itemContentView.getText();
if (text.charAt(text.length() - 1) == ELLIPSIS.charAt(0)) {
expand();
} else if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
ellipsize();
}
}

private void expand() {
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
itemContentView.setText(commentText);
linkify();
determineLinkFocus();
linkifyCommentContentView(v -> determineMovementMethod());
}

private void linkify() {
LinkifyCompat.addLinks(itemContentView, Linkify.WEB_URLS);
LinkifyCompat.addLinks(itemContentView, TimestampExtractor.TIMESTAMPS_PATTERN, null, null,
(match, url) -> {
try {
final var timestampMatch = TimestampExtractor
.getTimestampFromMatcher(match, commentText);
if (timestampMatch == null) {
return url;
}
return streamUrl + url.replace(Objects.requireNonNull(match.group(0)),
"#timestamp=" + timestampMatch.seconds());
} catch (final Exception ex) {
Log.e(TAG, "Unable to process url='" + url + "' as timestampLink", ex);
return url;
}
});
private void linkifyCommentContentView(@Nullable final Consumer<TextView> onCompletion) {
disposables.clear();
if (commentText != null) {
TextLinkifier.fromDescription(itemContentView, commentText,
HtmlCompat.FROM_HTML_MODE_LEGACY, streamService, streamUrl, disposables,
onCompletion);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.schabi.newpipe.util;

import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;

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

metaInfoSeparator.setVisibility(View.VISIBLE);
TextLinkifier.createLinksFromHtmlBlock(metaInfoTextView, stringBuilder.toString(),
HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, null, disposables);
TextLinkifier.fromHtml(metaInfoTextView, stringBuilder.toString(),
HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, null, null, disposables,
SET_LINK_MOVEMENT_METHOD);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,37 @@

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

import android.text.Selection;
import android.text.Spannable;
import android.annotation.SuppressLint;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

import org.schabi.newpipe.util.external_communication.ShareUtils;

import io.reactivex.rxjava3.disposables.CompositeDisposable;

public class CommentTextOnTouchListener implements View.OnTouchListener {
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();

@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(final View v, final MotionEvent event) {
if (!(v instanceof TextView)) {
return false;
}
final TextView widget = (TextView) v;
final Object text = widget.getText();
final CharSequence text = widget.getText();
if (text instanceof Spanned) {
final Spannable buffer = (Spannable) text;

final Spanned buffer = (Spanned) text;
final int action = event.getAction();

if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
final int offset = getOffsetForHorizontalLine(widget, event);
final ClickableSpan[] link = buffer.getSpans(offset, offset, ClickableSpan.class);
final ClickableSpan[] links = buffer.getSpans(offset, offset, ClickableSpan.class);

if (link.length != 0) {
if (links.length != 0) {
if (action == MotionEvent.ACTION_UP) {
if (link[0] instanceof URLSpan) {
final String url = ((URLSpan) link[0]).getURL();
if (!InternalUrlsHandler.handleUrlCommentsTimestamp(
new CompositeDisposable(), v.getContext(), url)) {
ShareUtils.openUrlInBrowser(v.getContext(), url, false);
}
}
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
links[0].onClick(widget);
}
// we handle events that intersect links, so return true
return true;
}
}
Expand Down
Loading

0 comments on commit c038f8c

Please sign in to comment.