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;
|
||||
}
|
||||
|
||||
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());
|
||||
|
|
|
@ -280,8 +280,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
|
||||
handleSearchSuggestion();
|
||||
|
||||
showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
||||
metaInfoTextView, metaInfoSeparator);
|
||||
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
||||
metaInfoTextView, metaInfoSeparator));
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||
initSuggestionObserver();
|
||||
|
@ -1002,11 +1002,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||
// List<MetaInfo> 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();
|
||||
|
||||
|
|
|
@ -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<MetaInfo> metaInfos,
|
||||
final TextView metaInfoTextView,
|
||||
final View metaInfoSeparator) {
|
||||
public static Disposable showMetaInfoInTextView(@Nullable final List<MetaInfo> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|||
* <p>
|
||||
* 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 {
|
|||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <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.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue