From 594f0b10ba379b24669bfcb4c04a0f5c3537ea50 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Jan 2021 20:57:19 +0100 Subject: [PATCH] Move TextLinkifier computation out of main thread --- .../fragments/detail/VideoDetailFragment.java | 31 +++--- .../fragments/list/search/SearchFragment.java | 8 +- .../schabi/newpipe/util/ExtractorHelper.java | 16 +-- .../schabi/newpipe/util/TextLinkifier.java | 100 +++++++++++------- 4 files changed, 92 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 7ebf66390..9079c47ca 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1229,19 +1229,20 @@ public final class VideoDetailFragment return; } - if (description.getType() == Description.HTML) { - TextLinkifier.createLinksFromHtmlBlock(requireContext(), description.getContent(), - videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY); - videoDescriptionView.setVisibility(View.VISIBLE); - } else if (description.getType() == Description.MARKDOWN) { - TextLinkifier.createLinksFromMarkdownText(requireContext(), description.getContent(), - videoDescriptionView); - videoDescriptionView.setVisibility(View.VISIBLE); - } else { - //== Description.PLAIN_TEXT - TextLinkifier.createLinksFromPlainText(requireContext(), description.getContent(), - videoDescriptionView); - videoDescriptionView.setVisibility(View.VISIBLE); + switch (description.getType()) { + case Description.HTML: + disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(), + description.getContent(), videoDescriptionView, + HtmlCompat.FROM_HTML_MODE_LEGACY)); + break; + case Description.MARKDOWN: + disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(), + description.getContent(), videoDescriptionView)); + break; + case Description.PLAIN_TEXT: default: + disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(), + description.getContent(), videoDescriptionView)); + break; } } @@ -1557,8 +1558,8 @@ public final class VideoDetailFragment prepareDescription(info.getDescription()); updateProgressInfo(info); initThumbnailViews(info); - showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator); - + disposables.add(showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, + detailMetaInfoSeparator)); if (player == null || player.isStopped()) { updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 67663a073..b15bb97f2 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -280,8 +280,8 @@ public class SearchFragment extends BaseListFragment cannot be bundled without creating some containers metaInfo = new MetaInfo[result.getMetaInfo().size()]; metaInfo = result.getMetaInfo().toArray(metaInfo); + disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, + metaInfoSeparator)); handleSearchSuggestion(); - showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator); - lastSearchedString = searchString; nextPage = result.getNextPage(); diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 6938cf4cc..6ee69dcd9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -22,7 +22,6 @@ package org.schabi.newpipe.util; import android.content.Context; import android.content.Intent; import android.os.Handler; -import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.View; import android.widget.TextView; @@ -68,6 +67,7 @@ import java.util.List; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.disposables.Disposable; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; @@ -325,10 +325,11 @@ public final class ExtractorHelper { * @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 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 metaInfos, - final TextView metaInfoTextView, - final View metaInfoSeparator) { + public static Disposable showMetaInfoInTextView(@Nullable final List metaInfos, + final TextView metaInfoTextView, + final View metaInfoSeparator) { final Context context = metaInfoTextView.getContext(); final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.show_meta_info_key), true); @@ -336,6 +337,7 @@ public final class ExtractorHelper { if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) { metaInfoTextView.setVisibility(View.GONE); metaInfoSeparator.setVisibility(View.GONE); + return Disposable.empty(); } else { 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); + return TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(), + metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java index eb7296611..087677333 100644 --- a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java +++ b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java @@ -6,15 +6,23 @@ import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.text.util.Linkify; +import android.util.Log; import android.view.View; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.core.text.HtmlCompat; import io.noties.markwon.Markwon; 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 static final String TAG = TextLinkifier.class.getSimpleName(); + private TextLinkifier() { } @@ -23,20 +31,21 @@ public final class TextLinkifier { *

* This will call * {@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 htmlBlock the htmlBlock to be linked * @param textView the TextView to set the htmlBlock linked * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)} * will be called + * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed */ - public static void createLinksFromHtmlBlock(final Context context, - final String htmlBlock, - final TextView textView, - final int htmlCompatFlag) { - changeIntentsOfDescriptionLinks(context, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), - textView); + public static Disposable createLinksFromHtmlBlock(final Context context, + final String htmlBlock, + final TextView textView, + final int htmlCompatFlag) { + return changeIntentsOfDescriptionLinks(context, + HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView); } /** @@ -44,19 +53,20 @@ public final class TextLinkifier { *

* This will call * {@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)}. * * @param context the context to use * @param plainTextBlock the block of plain text to be 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, - final String plainTextBlock, - final TextView textView) { + public static Disposable createLinksFromPlainText(final Context context, + final String plainTextBlock, + final TextView textView) { textView.setAutoLinkMask(Linkify.WEB_URLS); textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE); - changeIntentsOfDescriptionLinks(context, textView.getText(), textView); + return changeIntentsOfDescriptionLinks(context, textView.getText(), textView); } /** @@ -70,48 +80,66 @@ public final class TextLinkifier { * @param context the context to use * @param markdownBlock the block of markdown text to be 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, - final String markdownBlock, - final TextView textView) { + public static Disposable createLinksFromMarkdownText(final Context context, + final String markdownBlock, + final TextView textView) { final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build(); 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. *

- * Instead of using an ACTION_VIEW intent in the description of a content, this method will - * parse the CharSequence and replace all current web links with - * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}. + * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of a + * content, this method will parse the {@link CharSequence} and replace all current web links + * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}. *

- * 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. * * @param context the context to use * @param chars the CharSequence to be parsed * @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, - final CharSequence chars, - final TextView textView) { - final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars); - final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class); + private static Disposable changeIntentsOfDescriptionLinks(final Context context, + final CharSequence chars, + final TextView textView) { + return Single.fromCallable(() -> { + final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars); + final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class); - for (final URLSpan span : urls) { - final ClickableSpan clickableSpan = new ClickableSpan() { - public void onClick(final View view) { - ShareUtils.openUrlInBrowser(context, span.getURL(), false); - } - }; + for (final URLSpan span : urls) { + final ClickableSpan clickableSpan = new ClickableSpan() { + public void onClick(@NonNull final View view) { + ShareUtils.openUrlInBrowser(context, span.getURL(), false); + } + }; - textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span), - textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span)); - textBlockLinked.removeSpan(span); - } + textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span), + textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(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.setVisibility(View.VISIBLE); } }