Improve text linkifier function parameters

This commit is contained in:
Stypox 2021-06-05 14:59:23 +02:00 committed by TiA4f8R
parent 218f25c171
commit eef418a757
No known key found for this signature in database
GPG Key ID: E6D3E7F5949450DD
4 changed files with 74 additions and 94 deletions

View File

@ -16,9 +16,9 @@ import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.ActivityAboutBinding import org.schabi.newpipe.databinding.ActivityAboutBinding
import org.schabi.newpipe.databinding.FragmentAboutBinding import org.schabi.newpipe.databinding.FragmentAboutBinding
import org.schabi.newpipe.util.external_communication.ShareUtils
import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper
import org.schabi.newpipe.util.external_communication.ShareUtils
class AboutActivity : AppCompatActivity() { class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -19,7 +19,6 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentDescriptionBinding; import org.schabi.newpipe.databinding.FragmentDescriptionBinding;
import org.schabi.newpipe.databinding.ItemMetadataBinding; import org.schabi.newpipe.databinding.ItemMetadataBinding;
import org.schabi.newpipe.databinding.ItemMetadataTagsBinding; import org.schabi.newpipe.databinding.ItemMetadataTagsBinding;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.Description; import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
@ -132,24 +131,19 @@ public class DescriptionFragment extends BaseFragment {
private void loadDescriptionContent() { private void loadDescriptionContent() {
final Description description = streamInfo.getDescription(); final Description description = streamInfo.getDescription();
final String contentUrl = streamInfo.getUrl();
final StreamingService service = streamInfo.getService();
switch (description.getType()) { switch (description.getType()) {
case Description.HTML: case Description.HTML:
descriptionDisposable = TextLinkifier.createLinksFromHtmlBlock(requireContext(), descriptionDisposable = TextLinkifier.createLinksFromHtmlBlock(
description.getContent(), binding.detailDescriptionView, binding.detailDescriptionView, description.getContent(),
service, contentUrl, HtmlCompat.FROM_HTML_MODE_LEGACY); HtmlCompat.FROM_HTML_MODE_LEGACY, streamInfo);
break; break;
case Description.MARKDOWN: case Description.MARKDOWN:
descriptionDisposable = TextLinkifier.createLinksFromMarkdownText(requireContext(), descriptionDisposable = TextLinkifier.createLinksFromMarkdownText(
description.getContent(), binding.detailDescriptionView, binding.detailDescriptionView, description.getContent(), streamInfo);
service, contentUrl);
break; break;
case Description.PLAIN_TEXT: default: case Description.PLAIN_TEXT: default:
descriptionDisposable = TextLinkifier.createLinksFromPlainText(requireContext(), descriptionDisposable = TextLinkifier.createLinksFromPlainText(
description.getContent(), binding.detailDescriptionView, binding.detailDescriptionView, description.getContent(), streamInfo);
service, contentUrl);
break; break;
} }
} }
@ -204,8 +198,7 @@ public class DescriptionFragment extends BaseFragment {
}); });
if (linkifyContent) { if (linkifyContent) {
TextLinkifier.createLinksFromPlainText(requireContext(), TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content, null);
content, itemBinding.metadataContentView, null, null);
} else { } else {
itemBinding.metadataContentView.setText(content); itemBinding.metadataContentView.setText(content);
} }

View File

@ -311,9 +311,9 @@ public final class ExtractorHelper {
} }
metaInfoSeparator.setVisibility(View.VISIBLE); metaInfoSeparator.setVisibility(View.VISIBLE);
return TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(), return TextLinkifier.createLinksFromHtmlBlock(metaInfoTextView,
metaInfoTextView, null, null, stringBuilder.toString(), HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING,
HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); null);
} }
} }

View File

@ -11,9 +11,10 @@ import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat; import androidx.core.text.HtmlCompat;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -42,85 +43,74 @@ public final class TextLinkifier {
* Create web links for contents with an HTML description. * Create web links for contents with an HTML description.
* <p> * <p>
* This will call * This will call
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView, * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
* StreamingService, String)}
* after having 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 textView the TextView to set the htmlBlock linked
* @param htmlBlock the htmlBlock to be linked * @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)}
* @param streamingService the {@link StreamingService} of the content * will be called
* @param contentUrl the URL of the content * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)} * the specific time, and hashtags to search for the term in the correct
* will be called * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/ */
@NonNull @NonNull
public static Disposable createLinksFromHtmlBlock(final Context context, public static Disposable createLinksFromHtmlBlock(@NonNull final TextView textView,
final String htmlBlock, final String htmlBlock,
final TextView textView, final int htmlCompatFlag,
final StreamingService streamingService, @Nullable final Info relatedInfo) {
final String contentUrl, return changeIntentsOfDescriptionLinks(
final int htmlCompatFlag) { textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfo);
return changeIntentsOfDescriptionLinks(context,
HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView, streamingService,
contentUrl);
} }
/** /**
* Create web links for contents with a plain text description. * Create web links for contents with a plain text description.
* <p> * <p>
* This will call * This will call
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView, * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
* StreamingService, String)}
* after having 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 textView the TextView to set the plain text block linked
* @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 relatedInfo if given, handle timestamps to open the stream in the popup player at
* @param streamingService the {@link StreamingService} of the content * the specific time, and hashtags to search for the term in the correct
* @param contentUrl the URL of the content * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/ */
@NonNull @NonNull
public static Disposable createLinksFromPlainText(final Context context, public static Disposable createLinksFromPlainText(@NonNull final TextView textView,
final String plainTextBlock, final String plainTextBlock,
@NonNull final TextView textView, @Nullable final Info relatedInfo) {
final StreamingService streamingService,
final String contentUrl) {
textView.setAutoLinkMask(Linkify.WEB_URLS); textView.setAutoLinkMask(Linkify.WEB_URLS);
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE); textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
return changeIntentsOfDescriptionLinks(context, textView.getText(), textView, return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo);
streamingService, contentUrl);
} }
/** /**
* Create web links for contents with a markdown description. * Create web links for contents with a markdown description.
* <p> * <p>
* This will call * This will call
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView, * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
* StreamingService, String)}
* after creating an {@link Markwon} object and using * after creating an {@link Markwon} object and using
* {@link Markwon#setMarkdown(TextView, String)}. * {@link Markwon#setMarkdown(TextView, String)}.
* *
* @param context the context to use * @param textView the TextView to set the plain text block linked
* @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 relatedInfo if given, handle timestamps to open the stream in the popup player at
* @param streamingService the {@link StreamingService} of the content * the specific time, and hashtags to search for the term in the correct
* @param contentUrl the URL of the content * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/ */
@NonNull @NonNull
public static Disposable createLinksFromMarkdownText(final Context context, public static Disposable createLinksFromMarkdownText(@NonNull final TextView textView,
final String markdownBlock, final String markdownBlock,
final TextView textView, @Nullable final Info relatedInfo) {
final StreamingService streamingService, final Markwon markwon = Markwon.builder(textView.getContext())
final String contentUrl) { .usePlugin(LinkifyPlugin.create()).build();
final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
markwon.setMarkdown(textView, markdownBlock); markwon.setMarkdown(textView, markdownBlock);
return changeIntentsOfDescriptionLinks(context, textView.getText(), textView, return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo);
streamingService, contentUrl);
} }
/** /**
@ -134,12 +124,12 @@ public final class TextLinkifier {
* @param context the context to use * @param context the context to use
* @param spannableDescription the SpannableStringBuilder with the text of the * @param spannableDescription the SpannableStringBuilder with the text of the
* content description * content description
* @param streamingService the {@link StreamingService} of the content * @param relatedInfo used to search for the term in the correct service
*/ */
private static void addClickListenersOnHashtags(final Context context, private static void addClickListenersOnHashtags(final Context context,
@NonNull final SpannableStringBuilder @NonNull final SpannableStringBuilder
spannableDescription, spannableDescription,
final StreamingService streamingService) { final Info relatedInfo) {
final String descriptionText = spannableDescription.toString(); final String descriptionText = spannableDescription.toString();
final Matcher hashtagsMatches = HASHTAGS_PATTERN.matcher(descriptionText); final Matcher hashtagsMatches = HASHTAGS_PATTERN.matcher(descriptionText);
@ -155,7 +145,7 @@ public final class TextLinkifier {
spannableDescription.setSpan(new ClickableSpan() { spannableDescription.setSpan(new ClickableSpan() {
@Override @Override
public void onClick(@NonNull final View view) { public void onClick(@NonNull final View view) {
NavigationHelper.openSearch(context, streamingService.getServiceId(), NavigationHelper.openSearch(context, relatedInfo.getServiceId(),
parsedHashtag); parsedHashtag);
} }
}, hashtagStart, hashtagEnd, 0); }, hashtagStart, hashtagEnd, 0);
@ -173,14 +163,12 @@ public final class TextLinkifier {
* @param context the context to use * @param context the context to use
* @param spannableDescription the SpannableStringBuilder with the text of the * @param spannableDescription the SpannableStringBuilder with the text of the
* content description * content description
* @param contentUrl the URL of the content * @param relatedInfo what to open in the popup player when timestamps are clicked
* @param streamingService the {@link StreamingService} of the content
*/ */
private static void addClickListenersOnTimestamps(final Context context, private static void addClickListenersOnTimestamps(final Context context,
@NonNull final SpannableStringBuilder @NonNull final SpannableStringBuilder
spannableDescription, spannableDescription,
final String contentUrl, final Info relatedInfo) {
final StreamingService streamingService) {
final String descriptionText = spannableDescription.toString(); final String descriptionText = spannableDescription.toString();
final Matcher timestampsMatches = TIMESTAMPS_PATTERN.matcher(descriptionText); final Matcher timestampsMatches = TIMESTAMPS_PATTERN.matcher(descriptionText);
@ -189,14 +177,14 @@ public final class TextLinkifier {
final int timestampEnd = timestampsMatches.end(3); final int timestampEnd = timestampsMatches.end(3);
final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd); final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd);
final String[] timestampParts = parsedTimestamp.split(":"); final String[] timestampParts = parsedTimestamp.split(":");
final int time;
final int seconds;
if (timestampParts.length == 3) { // timestamp format: XX:XX:XX if (timestampParts.length == 3) { // timestamp format: XX:XX:XX
time = Integer.parseInt(timestampParts[0]) * 3600 // hours seconds = Integer.parseInt(timestampParts[0]) * 3600 // hours
+ Integer.parseInt(timestampParts[1]) * 60 // minutes + Integer.parseInt(timestampParts[1]) * 60 // minutes
+ Integer.parseInt(timestampParts[2]); // seconds + Integer.parseInt(timestampParts[2]); // seconds
} else if (timestampParts.length == 2) { // timestamp format: XX:XX } else if (timestampParts.length == 2) { // timestamp format: XX:XX
time = Integer.parseInt(timestampParts[0]) * 60 // minutes seconds = Integer.parseInt(timestampParts[0]) * 60 // minutes
+ Integer.parseInt(timestampParts[1]); // seconds + Integer.parseInt(timestampParts[1]); // seconds
} else { } else {
continue; continue;
@ -205,8 +193,8 @@ public final class TextLinkifier {
spannableDescription.setSpan(new ClickableSpan() { spannableDescription.setSpan(new ClickableSpan() {
@Override @Override
public void onClick(@NonNull final View view) { public void onClick(@NonNull final View view) {
playOnPopup(new CompositeDisposable(), context, contentUrl, streamingService, playOnPopup(new CompositeDisposable(), context, relatedInfo.getUrl(),
time); relatedInfo.getService(), seconds);
} }
}, timestampStart, timestampEnd, 0); }, timestampStart, timestampEnd, 0);
} }
@ -221,28 +209,28 @@ public final class TextLinkifier {
* with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}. * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
* This method will also add click listeners on timestamps in this description, which will play * This method will also add click listeners on timestamps in this description, which will play
* the content in the popup player at the time indicated in the timestamp, by using * the content in the popup player at the time indicated in the timestamp, by using
* {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, String, * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, Info)}
* StreamingService)} method and click listeners on hashtags, which will open a search * method and click listeners on hashtags, by using
* on the current service with the hashtag. * {@link TextLinkifier#addClickListenersOnHashtags(Context, SpannableStringBuilder, Info)},
* which will open a search on the current service with the hashtag.
* <p> * <p>
* This method is required in order to intercept links and e.g. 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 textView the TextView in which the converted CharSequence will be applied
* @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 relatedInfo if given, handle timestamps to open the stream in the popup player at
* @param streamingService the {@link StreamingService} of the content * the specific time, and hashtags to search for the term in the correct
* @param contentUrl the URL of the content * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/ */
@NonNull @NonNull
private static Disposable changeIntentsOfDescriptionLinks(final Context context, private static Disposable changeIntentsOfDescriptionLinks(final TextView textView,
final CharSequence chars, final CharSequence chars,
final TextView textView, @Nullable final Info relatedInfo) {
final StreamingService
streamingService,
final String contentUrl) {
return Single.fromCallable(() -> { return Single.fromCallable(() -> {
final Context context = textView.getContext();
// add custom click actions on web links // add custom click actions on web links
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);
@ -264,11 +252,10 @@ public final class TextLinkifier {
} }
// add click actions on plain text timestamps only for description of contents, // add click actions on plain text timestamps only for description of contents,
// unneeded for metainfo TextViews // unneeded for meta-info or other TextViews
if (contentUrl != null || streamingService != null) { if (relatedInfo != null) {
addClickListenersOnTimestamps(context, textBlockLinked, contentUrl, addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo);
streamingService); addClickListenersOnHashtags(context, textBlockLinked, relatedInfo);
addClickListenersOnHashtags(context, textBlockLinked, streamingService);
} }
return textBlockLinked; return textBlockLinked;