Move TextLinkifier computation out of main thread
This commit is contained in:
parent
79e98db3bd
commit
594f0b10ba
4 changed files with 92 additions and 63 deletions
|
@ -1229,19 +1229,20 @@ public final class VideoDetailFragment
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (description.getType() == Description.HTML) {
|
switch (description.getType()) {
|
||||||
TextLinkifier.createLinksFromHtmlBlock(requireContext(), description.getContent(),
|
case Description.HTML:
|
||||||
videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY);
|
disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(),
|
||||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
description.getContent(), videoDescriptionView,
|
||||||
} else if (description.getType() == Description.MARKDOWN) {
|
HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||||
TextLinkifier.createLinksFromMarkdownText(requireContext(), description.getContent(),
|
break;
|
||||||
videoDescriptionView);
|
case Description.MARKDOWN:
|
||||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(),
|
||||||
} else {
|
description.getContent(), videoDescriptionView));
|
||||||
//== Description.PLAIN_TEXT
|
break;
|
||||||
TextLinkifier.createLinksFromPlainText(requireContext(), description.getContent(),
|
case Description.PLAIN_TEXT: default:
|
||||||
videoDescriptionView);
|
disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(),
|
||||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
description.getContent(), videoDescriptionView));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1557,8 +1558,8 @@ public final class VideoDetailFragment
|
||||||
prepareDescription(info.getDescription());
|
prepareDescription(info.getDescription());
|
||||||
updateProgressInfo(info);
|
updateProgressInfo(info);
|
||||||
initThumbnailViews(info);
|
initThumbnailViews(info);
|
||||||
showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator);
|
disposables.add(showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView,
|
||||||
|
detailMetaInfoSeparator));
|
||||||
|
|
||||||
if (player == null || player.isStopped()) {
|
if (player == null || player.isStopped()) {
|
||||||
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
|
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
|
||||||
|
|
|
@ -280,8 +280,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
|
|
||||||
handleSearchSuggestion();
|
handleSearchSuggestion();
|
||||||
|
|
||||||
showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
||||||
metaInfoTextView, metaInfoSeparator);
|
metaInfoTextView, metaInfoSeparator));
|
||||||
|
|
||||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||||
initSuggestionObserver();
|
initSuggestionObserver();
|
||||||
|
@ -1002,11 +1002,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
// List<MetaInfo> cannot be bundled without creating some containers
|
// List<MetaInfo> cannot be bundled without creating some containers
|
||||||
metaInfo = new MetaInfo[result.getMetaInfo().size()];
|
metaInfo = new MetaInfo[result.getMetaInfo().size()];
|
||||||
metaInfo = result.getMetaInfo().toArray(metaInfo);
|
metaInfo = result.getMetaInfo().toArray(metaInfo);
|
||||||
|
disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView,
|
||||||
|
metaInfoSeparator));
|
||||||
|
|
||||||
handleSearchSuggestion();
|
handleSearchSuggestion();
|
||||||
|
|
||||||
showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator);
|
|
||||||
|
|
||||||
lastSearchedString = searchString;
|
lastSearchedString = searchString;
|
||||||
nextPage = result.getNextPage();
|
nextPage = result.getNextPage();
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ package org.schabi.newpipe.util;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -68,6 +67,7 @@ import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Maybe;
|
import io.reactivex.rxjava3.core.Maybe;
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
|
@ -325,8 +325,9 @@ public final class ExtractorHelper {
|
||||||
* @param metaInfos a list of meta information, can be null or empty
|
* @param metaInfos a list of meta information, can be null or empty
|
||||||
* @param metaInfoTextView the text view in which to show the formatted HTML
|
* @param metaInfoTextView the text view in which to show the formatted HTML
|
||||||
* @param metaInfoSeparator another view to be shown or hidden accordingly to the text view
|
* @param metaInfoSeparator another view to be shown or hidden accordingly to the text view
|
||||||
|
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
|
||||||
*/
|
*/
|
||||||
public static void showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInfos,
|
public static Disposable showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInfos,
|
||||||
final TextView metaInfoTextView,
|
final TextView metaInfoTextView,
|
||||||
final View metaInfoSeparator) {
|
final View metaInfoSeparator) {
|
||||||
final Context context = metaInfoTextView.getContext();
|
final Context context = metaInfoTextView.getContext();
|
||||||
|
@ -336,6 +337,7 @@ public final class ExtractorHelper {
|
||||||
if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
|
if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
|
||||||
metaInfoTextView.setVisibility(View.GONE);
|
metaInfoTextView.setVisibility(View.GONE);
|
||||||
metaInfoSeparator.setVisibility(View.GONE);
|
metaInfoSeparator.setVisibility(View.GONE);
|
||||||
|
return Disposable.empty();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
final StringBuilder stringBuilder = new StringBuilder();
|
final StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
@ -365,11 +367,9 @@ public final class ExtractorHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(),
|
|
||||||
metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
|
|
||||||
metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
metaInfoTextView.setVisibility(View.VISIBLE);
|
|
||||||
metaInfoSeparator.setVisibility(View.VISIBLE);
|
metaInfoSeparator.setVisibility(View.VISIBLE);
|
||||||
|
return TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(),
|
||||||
|
metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,23 @@ import android.text.method.LinkMovementMethod;
|
||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
|
|
||||||
import io.noties.markwon.Markwon;
|
import io.noties.markwon.Markwon;
|
||||||
import io.noties.markwon.linkify.LinkifyPlugin;
|
import io.noties.markwon.linkify.LinkifyPlugin;
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
public final class TextLinkifier {
|
public final class TextLinkifier {
|
||||||
|
public static final String TAG = TextLinkifier.class.getSimpleName();
|
||||||
|
|
||||||
private TextLinkifier() {
|
private TextLinkifier() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,20 +31,21 @@ public final class TextLinkifier {
|
||||||
* <p>
|
* <p>
|
||||||
* This will call
|
* This will call
|
||||||
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
|
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
|
||||||
* after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
|
* after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
|
||||||
*
|
*
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param htmlBlock the htmlBlock to be linked
|
* @param htmlBlock the htmlBlock to be linked
|
||||||
* @param textView the TextView to set the htmlBlock linked
|
* @param textView the TextView to set the htmlBlock linked
|
||||||
* @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
|
* @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
|
||||||
* will be called
|
* will be called
|
||||||
|
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
|
||||||
*/
|
*/
|
||||||
public static void createLinksFromHtmlBlock(final Context context,
|
public static Disposable createLinksFromHtmlBlock(final Context context,
|
||||||
final String htmlBlock,
|
final String htmlBlock,
|
||||||
final TextView textView,
|
final TextView textView,
|
||||||
final int htmlCompatFlag) {
|
final int htmlCompatFlag) {
|
||||||
changeIntentsOfDescriptionLinks(context, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag),
|
return changeIntentsOfDescriptionLinks(context,
|
||||||
textView);
|
HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,19 +53,20 @@ public final class TextLinkifier {
|
||||||
* <p>
|
* <p>
|
||||||
* This will call
|
* This will call
|
||||||
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
|
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
|
||||||
* after linked the URLs with {@link TextView#setAutoLinkMask(int)} and
|
* after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and
|
||||||
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
|
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
|
||||||
*
|
*
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param plainTextBlock the block of plain text to be linked
|
* @param plainTextBlock the block of plain text to be linked
|
||||||
* @param textView the TextView to set the plain text block linked
|
* @param textView the TextView to set the plain text block linked
|
||||||
|
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
|
||||||
*/
|
*/
|
||||||
public static void createLinksFromPlainText(final Context context,
|
public static Disposable createLinksFromPlainText(final Context context,
|
||||||
final String plainTextBlock,
|
final String plainTextBlock,
|
||||||
final TextView textView) {
|
final TextView textView) {
|
||||||
textView.setAutoLinkMask(Linkify.WEB_URLS);
|
textView.setAutoLinkMask(Linkify.WEB_URLS);
|
||||||
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
|
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
|
||||||
changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
|
return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,38 +80,41 @@ public final class TextLinkifier {
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param markdownBlock the block of markdown text to be linked
|
* @param markdownBlock the block of markdown text to be linked
|
||||||
* @param textView the TextView to set the plain text block linked
|
* @param textView the TextView to set the plain text block linked
|
||||||
|
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
|
||||||
*/
|
*/
|
||||||
public static void createLinksFromMarkdownText(final Context context,
|
public static Disposable createLinksFromMarkdownText(final Context context,
|
||||||
final String markdownBlock,
|
final String markdownBlock,
|
||||||
final TextView textView) {
|
final TextView textView) {
|
||||||
final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
|
final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
|
||||||
markwon.setMarkdown(textView, markdownBlock);
|
markwon.setMarkdown(textView, markdownBlock);
|
||||||
changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
|
return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change links generated by libraries in the description of a content to a custom link action.
|
* Change links generated by libraries in the description of a content to a custom link action.
|
||||||
* <p>
|
* <p>
|
||||||
* Instead of using an ACTION_VIEW intent in the description of a content, this method will
|
* Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of a
|
||||||
* parse the CharSequence and replace all current web links with
|
* content, this method will parse the {@link CharSequence} and replace all current web links
|
||||||
* {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
|
* with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
|
||||||
* <p>
|
* <p>
|
||||||
* This method is required in order to intercept links and maybe, show a confirmation dialog
|
* This method is required in order to intercept links and e.g. show a confirmation dialog
|
||||||
* before opening a web link.
|
* before opening a web link.
|
||||||
*
|
*
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param chars the CharSequence to be parsed
|
* @param chars the CharSequence to be parsed
|
||||||
* @param textView the TextView in which the converted CharSequence will be applied
|
* @param textView the TextView in which the converted CharSequence will be applied
|
||||||
|
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
|
||||||
*/
|
*/
|
||||||
private static void changeIntentsOfDescriptionLinks(final Context context,
|
private static Disposable changeIntentsOfDescriptionLinks(final Context context,
|
||||||
final CharSequence chars,
|
final CharSequence chars,
|
||||||
final TextView textView) {
|
final TextView textView) {
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
|
final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
|
||||||
final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
|
final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
|
||||||
|
|
||||||
for (final URLSpan span : urls) {
|
for (final URLSpan span : urls) {
|
||||||
final ClickableSpan clickableSpan = new ClickableSpan() {
|
final ClickableSpan clickableSpan = new ClickableSpan() {
|
||||||
public void onClick(final View view) {
|
public void onClick(@NonNull final View view) {
|
||||||
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
|
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -111,7 +124,22 @@ public final class TextLinkifier {
|
||||||
textBlockLinked.removeSpan(span);
|
textBlockLinked.removeSpan(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
textView.setText(textBlockLinked);
|
return textBlockLinked;
|
||||||
|
}).subscribeOn(Schedulers.computation())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
textBlockLinked -> setTextViewCharSequence(textView, textBlockLinked),
|
||||||
|
throwable -> {
|
||||||
|
Log.e(TAG, "Unable to linkify text", throwable);
|
||||||
|
// this should never happen, but if it does, just fallback to it
|
||||||
|
setTextViewCharSequence(textView, chars);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setTextViewCharSequence(final TextView textView,
|
||||||
|
final CharSequence charSequence) {
|
||||||
|
textView.setText(charSequence);
|
||||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
textView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue