Fix disposables handling for text linkifier

also use differently Markwon methods to convert plain text to markdown
This commit is contained in:
Stypox 2021-06-05 15:35:48 +02:00 committed by TiA4f8R
parent eef418a757
commit edfe0f9c30
No known key found for this signature in database
GPG Key ID: E6D3E7F5949450DD
6 changed files with 101 additions and 104 deletions

View File

@ -31,7 +31,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import icepick.State; import icepick.State;
import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import static android.text.TextUtils.isEmpty; import static android.text.TextUtils.isEmpty;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
@ -41,8 +41,7 @@ public class DescriptionFragment extends BaseFragment {
@State @State
StreamInfo streamInfo = null; StreamInfo streamInfo = null;
@Nullable final CompositeDisposable descriptionDisposables = new CompositeDisposable();
Disposable descriptionDisposable = null;
FragmentDescriptionBinding binding; FragmentDescriptionBinding binding;
public DescriptionFragment() { public DescriptionFragment() {
@ -67,10 +66,8 @@ public class DescriptionFragment extends BaseFragment {
@Override @Override
public void onDestroy() { public void onDestroy() {
descriptionDisposables.clear();
super.onDestroy(); super.onDestroy();
if (descriptionDisposable != null) {
descriptionDisposable.dispose();
}
} }
@ -133,17 +130,17 @@ public class DescriptionFragment extends BaseFragment {
final Description description = streamInfo.getDescription(); final Description description = streamInfo.getDescription();
switch (description.getType()) { switch (description.getType()) {
case Description.HTML: case Description.HTML:
descriptionDisposable = TextLinkifier.createLinksFromHtmlBlock( TextLinkifier.createLinksFromHtmlBlock(binding.detailDescriptionView,
binding.detailDescriptionView, description.getContent(), description.getContent(), HtmlCompat.FROM_HTML_MODE_LEGACY, streamInfo,
HtmlCompat.FROM_HTML_MODE_LEGACY, streamInfo); descriptionDisposables);
break; break;
case Description.MARKDOWN: case Description.MARKDOWN:
descriptionDisposable = TextLinkifier.createLinksFromMarkdownText( TextLinkifier.createLinksFromMarkdownText(binding.detailDescriptionView,
binding.detailDescriptionView, description.getContent(), streamInfo); description.getContent(), streamInfo, descriptionDisposables);
break; break;
case Description.PLAIN_TEXT: default: case Description.PLAIN_TEXT: default:
descriptionDisposable = TextLinkifier.createLinksFromPlainText( TextLinkifier.createLinksFromPlainText(binding.detailDescriptionView,
binding.detailDescriptionView, description.getContent(), streamInfo); description.getContent(), streamInfo, descriptionDisposables);
break; break;
} }
} }
@ -198,7 +195,8 @@ public class DescriptionFragment extends BaseFragment {
}); });
if (linkifyContent) { if (linkifyContent) {
TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content, null); TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content, null,
descriptionDisposables);
} else { } else {
itemBinding.metadataContentView.setText(content); itemBinding.metadataContentView.setText(content);
} }

View File

@ -1546,8 +1546,8 @@ public final class VideoDetailFragment
.getDefaultResolutionIndex(activity, sortedVideoStreams); .getDefaultResolutionIndex(activity, sortedVideoStreams);
updateProgressInfo(info); updateProgressInfo(info);
initThumbnailViews(info); initThumbnailViews(info);
disposables.add(showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView, showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
binding.detailMetaInfoSeparator)); binding.detailMetaInfoSeparator, disposables);
if (player == null || player.isStopped()) { if (player == null || player.isStopped()) {
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl()); updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());

View File

@ -278,8 +278,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
handleSearchSuggestion(); handleSearchSuggestion();
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo), showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator)); searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator,
disposables);
if (TextUtils.isEmpty(searchString) || wasSearchFocused) { if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
showKeyboardSearch(); showKeyboardSearch();
@ -841,7 +842,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
hideSuggestionsPanel(); hideSuggestionsPanel();
showMetaInfoInTextView(null, searchBinding.searchMetaInfoTextView, showMetaInfoInTextView(null, searchBinding.searchMetaInfoTextView,
searchBinding.searchMetaInfoSeparator); searchBinding.searchMetaInfoSeparator, disposables);
hideKeyboardSearch(); hideKeyboardSearch();
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString) disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
@ -986,8 +987,8 @@ 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(), showMetaInfoInTextView(result.getMetaInfo(), searchBinding.searchMetaInfoTextView,
searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator)); searchBinding.searchMetaInfoSeparator, disposables);
handleSearchSuggestion(); handleSearchSuggestion();

View File

@ -55,7 +55,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 io.reactivex.rxjava3.disposables.CompositeDisposable;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
@ -269,18 +269,19 @@ 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 * @param disposables disposables created by the method are added here and their lifecycle
* should be handled by the calling class
*/ */
public static Disposable showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInfos, public static void showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInfos,
final TextView metaInfoTextView, final TextView metaInfoTextView,
final View metaInfoSeparator) { final View metaInfoSeparator,
final CompositeDisposable disposables) {
final Context context = metaInfoTextView.getContext(); final Context context = metaInfoTextView.getContext();
if (metaInfos == null || metaInfos.isEmpty() if (metaInfos == null || metaInfos.isEmpty()
|| !PreferenceManager.getDefaultSharedPreferences(context).getBoolean( || !PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
context.getString(R.string.show_meta_info_key), true)) { context.getString(R.string.show_meta_info_key), true)) {
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();
@ -311,9 +312,8 @@ public final class ExtractorHelper {
} }
metaInfoSeparator.setVisibility(View.VISIBLE); metaInfoSeparator.setVisibility(View.VISIBLE);
return TextLinkifier.createLinksFromHtmlBlock(metaInfoTextView, TextLinkifier.createLinksFromHtmlBlock(metaInfoTextView, stringBuilder.toString(),
stringBuilder.toString(), HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING, null, disposables);
null);
} }
} }

View File

@ -23,8 +23,6 @@ import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
public final class InternalUrlsHandler { public final class InternalUrlsHandler {
private static final Pattern AMPERSAND_TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)"); private static final Pattern AMPERSAND_TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
private static final Pattern HASHTAG_TIMESTAMP_PATTERN = private static final Pattern HASHTAG_TIMESTAMP_PATTERN =
@ -50,7 +48,7 @@ public final class InternalUrlsHandler {
disposables, disposables,
final Context context, final Context context,
@NonNull final String url) { @NonNull final String url) {
return handleUrl(disposables, context, url, HASHTAG_TIMESTAMP_PATTERN); return handleUrl(context, url, HASHTAG_TIMESTAMP_PATTERN, disposables);
} }
/** /**
@ -70,7 +68,7 @@ public final class InternalUrlsHandler {
disposables, disposables,
final Context context, final Context context,
@NonNull final String url) { @NonNull final String url) {
return handleUrl(disposables, context, url, AMPERSAND_TIMESTAMP_PATTERN); return handleUrl(context, url, AMPERSAND_TIMESTAMP_PATTERN, disposables);
} }
/** /**
@ -80,42 +78,37 @@ public final class InternalUrlsHandler {
* service URL with a timestamp, the popup player will be opened and true will be returned; * service URL with a timestamp, the popup player will be opened and true will be returned;
* else, false will be returned. * else, false will be returned.
* *
* @param disposables a field of the Activity/Fragment class that calls this method
* @param context the context to use * @param context the context to use
* @param url the URL to check if it can be handled * @param url the URL to check if it can be handled
* @param pattern the pattern to use * @param pattern the pattern to use
* @param disposables a field of the Activity/Fragment class that calls this method
* @return true if the URL can be handled by NewPipe, false if it cannot * @return true if the URL can be handled by NewPipe, false if it cannot
*/ */
private static boolean handleUrl(@NonNull final CompositeDisposable disposables, private static boolean handleUrl(final Context context,
final Context context,
@NonNull final String url, @NonNull final String url,
@NonNull final Pattern pattern) { @NonNull final Pattern pattern,
final String matchedUrl; @NonNull final CompositeDisposable disposables) {
final Matcher matcher = pattern.matcher(url);
if (!matcher.matches()) {
return false;
}
final String matchedUrl = matcher.group(1);
final int seconds = Integer.parseInt(matcher.group(2));
final StreamingService service; final StreamingService service;
final StreamingService.LinkType linkType; final StreamingService.LinkType linkType;
final int seconds;
final Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
matchedUrl = matcher.group(1);
seconds = Integer.parseInt(matcher.group(2));
} else {
return false;
}
if (isNullOrEmpty(matchedUrl)) {
return false;
}
try { try {
service = NewPipe.getServiceByUrl(matchedUrl); service = NewPipe.getServiceByUrl(matchedUrl);
linkType = service.getLinkTypeByUrl(matchedUrl); linkType = service.getLinkTypeByUrl(matchedUrl);
if (linkType == StreamingService.LinkType.NONE) {
return false;
}
} catch (final ExtractionException e) { } catch (final ExtractionException e) {
return false; return false;
} }
if (linkType == StreamingService.LinkType.NONE) {
return false;
}
if (linkType == StreamingService.LinkType.STREAM && seconds != -1) { if (linkType == StreamingService.LinkType.STREAM && seconds != -1) {
return playOnPopup(disposables, context, matchedUrl, service, seconds); return playOnPopup(context, matchedUrl, service, seconds, disposables);
} else { } else {
NavigationHelper.openRouterActivity(context, matchedUrl); NavigationHelper.openRouterActivity(context, matchedUrl);
return true; return true;
@ -125,18 +118,19 @@ public final class InternalUrlsHandler {
/** /**
* Play a content in the floating player. * Play a content in the floating player.
* *
* @param disposables a field of the Activity/Fragment class that calls this method
* @param context the context to be used * @param context the context to be used
* @param url the URL of the content * @param url the URL of the content
* @param service the service of the content * @param service the service of the content
* @param seconds the position in seconds at which the floating player will start * @param seconds the position in seconds at which the floating player will start
* @param disposables disposables created by the method are added here and their lifecycle
* should be handled by the calling class
* @return true if the playback of the content has successfully started or false if not * @return true if the playback of the content has successfully started or false if not
*/ */
public static boolean playOnPopup(@NonNull final CompositeDisposable disposables, public static boolean playOnPopup(final Context context,
final Context context,
final String url, final String url,
@NonNull final StreamingService service, @NonNull final StreamingService service,
final int seconds) { final int seconds,
@NonNull final CompositeDisposable disposables) {
final LinkHandlerFactory factory = service.getStreamLHFactory(); final LinkHandlerFactory factory = service.getStreamLHFactory();
final String cleanUrl; final String cleanUrl;

View File

@ -25,7 +25,6 @@ import io.noties.markwon.linkify.LinkifyPlugin;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.util.external_communication.InternalUrlsHandler.playOnPopup; import static org.schabi.newpipe.util.external_communication.InternalUrlsHandler.playOnPopup;
@ -42,9 +41,9 @@ 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(TextView, CharSequence,
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)} * Info, CompositeDisposable)} after having linked the URLs with
* after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}. * {@link HtmlCompat#fromHtml(String, int)}.
* *
* @param textView the TextView to set the htmlBlock linked * @param textView the TextView to set the htmlBlock linked
* @param htmlBlock the htmlBlock to be linked * @param htmlBlock the htmlBlock to be linked
@ -53,23 +52,24 @@ public final class TextLinkifier {
* @param relatedInfo if given, handle timestamps to open the stream in the popup player at * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct * the specific time, and hashtags to search for the term in the correct
* service * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * @param disposables disposables created by the method are added here and their lifecycle
* should be handled by the calling class
*/ */
@NonNull public static void createLinksFromHtmlBlock(@NonNull final TextView textView,
public static Disposable createLinksFromHtmlBlock(@NonNull final TextView textView, final String htmlBlock,
final String htmlBlock, final int htmlCompatFlag,
final int htmlCompatFlag, @Nullable final Info relatedInfo,
@Nullable final Info relatedInfo) { final CompositeDisposable disposables) {
return changeIntentsOfDescriptionLinks( changeIntentsOfDescriptionLinks(
textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfo); textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfo, disposables);
} }
/** /**
* 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(TextView, CharSequence,
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)} * Info, CompositeDisposable)} after having linked the URLs with
* after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and * {@link TextView#setAutoLinkMask(int)} and
* {@link TextView#setText(CharSequence, TextView.BufferType)}. * {@link TextView#setText(CharSequence, TextView.BufferType)}.
* *
* @param textView the TextView to set the plain text block linked * @param textView the TextView to set the plain text block linked
@ -77,40 +77,40 @@ public final class TextLinkifier {
* @param relatedInfo if given, handle timestamps to open the stream in the popup player at * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct * the specific time, and hashtags to search for the term in the correct
* service * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * @param disposables disposables created by the method are added here and their lifecycle
* should be handled by the calling class
*/ */
@NonNull public static void createLinksFromPlainText(@NonNull final TextView textView,
public static Disposable createLinksFromPlainText(@NonNull final TextView textView, final String plainTextBlock,
final String plainTextBlock, @Nullable final Info relatedInfo,
@Nullable final Info relatedInfo) { final CompositeDisposable disposables) {
textView.setAutoLinkMask(Linkify.WEB_URLS); textView.setAutoLinkMask(Linkify.WEB_URLS);
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE); textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo); changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo, disposables);
} }
/** /**
* 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(TextView, CharSequence,
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)} * Info, CompositeDisposable)} 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 textView the TextView to set the plain text block linked * @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 relatedInfo if given, handle timestamps to open the stream in the popup player at * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct * the specific time, and hashtags to search for the term in the correct
* service * @param disposables disposables created by the method are added here and their lifecycle
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * should be handled by the calling class
*/ */
@NonNull public static void createLinksFromMarkdownText(@NonNull final TextView textView,
public static Disposable createLinksFromMarkdownText(@NonNull final TextView textView, final String markdownBlock,
final String markdownBlock, @Nullable final Info relatedInfo,
@Nullable final Info relatedInfo) { final CompositeDisposable disposables) {
final Markwon markwon = Markwon.builder(textView.getContext()) final Markwon markwon = Markwon.builder(textView.getContext())
.usePlugin(LinkifyPlugin.create()).build(); .usePlugin(LinkifyPlugin.create()).build();
markwon.setMarkdown(textView, markdownBlock); changeIntentsOfDescriptionLinks(textView, markwon.toMarkdown(markdownBlock), relatedInfo,
return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo); disposables);
} }
/** /**
@ -164,11 +164,14 @@ public final class TextLinkifier {
* @param spannableDescription the SpannableStringBuilder with the text of the * @param spannableDescription the SpannableStringBuilder with the text of the
* content description * content description
* @param relatedInfo what to open in the popup player when timestamps are clicked * @param relatedInfo what to open in the popup player when timestamps are clicked
* @param disposables disposables created by the method are added here and their
* lifecycle should be handled by the calling class
*/ */
private static void addClickListenersOnTimestamps(final Context context, private static void addClickListenersOnTimestamps(final Context context,
@NonNull final SpannableStringBuilder @NonNull final SpannableStringBuilder
spannableDescription, spannableDescription,
final Info relatedInfo) { final Info relatedInfo,
final CompositeDisposable disposables) {
final String descriptionText = spannableDescription.toString(); final String descriptionText = spannableDescription.toString();
final Matcher timestampsMatches = TIMESTAMPS_PATTERN.matcher(descriptionText); final Matcher timestampsMatches = TIMESTAMPS_PATTERN.matcher(descriptionText);
@ -193,8 +196,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, relatedInfo.getUrl(), playOnPopup(context, relatedInfo.getUrl(), relatedInfo.getService(), seconds,
relatedInfo.getService(), seconds); disposables);
} }
}, timestampStart, timestampEnd, 0); }, timestampStart, timestampEnd, 0);
} }
@ -209,8 +212,8 @@ 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, Info)} * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, Info,
* method and click listeners on hashtags, by using * CompositeDisposable)} method and click listeners on hashtags, by using
* {@link TextLinkifier#addClickListenersOnHashtags(Context, SpannableStringBuilder, Info)}, * {@link TextLinkifier#addClickListenersOnHashtags(Context, SpannableStringBuilder, Info)},
* which will open a search on the current service with the hashtag. * which will open a search on the current service with the hashtag.
* <p> * <p>
@ -222,13 +225,14 @@ public final class TextLinkifier {
* @param relatedInfo if given, handle timestamps to open the stream in the popup player at * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct * the specific time, and hashtags to search for the term in the correct
* service * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed * @param disposables disposables created by the method are added here and their lifecycle
* should be handled by the calling class
*/ */
@NonNull private static void changeIntentsOfDescriptionLinks(final TextView textView,
private static Disposable changeIntentsOfDescriptionLinks(final TextView textView, final CharSequence chars,
final CharSequence chars, @Nullable final Info relatedInfo,
@Nullable final Info relatedInfo) { final CompositeDisposable disposables) {
return Single.fromCallable(() -> { disposables.add(Single.fromCallable(() -> {
final Context context = textView.getContext(); final Context context = textView.getContext();
// add custom click actions on web links // add custom click actions on web links
@ -254,7 +258,7 @@ 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 meta-info or other TextViews // unneeded for meta-info or other TextViews
if (relatedInfo != null) { if (relatedInfo != null) {
addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo); addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo, disposables);
addClickListenersOnHashtags(context, textBlockLinked, relatedInfo); addClickListenersOnHashtags(context, textBlockLinked, relatedInfo);
} }
@ -267,7 +271,7 @@ public final class TextLinkifier {
Log.e(TAG, "Unable to linkify text", throwable); Log.e(TAG, "Unable to linkify text", throwable);
// this should never happen, but if it does, just fallback to it // this should never happen, but if it does, just fallback to it
setTextViewCharSequence(textView, chars); setTextViewCharSequence(textView, chars);
}); }));
} }
private static void setTextViewCharSequence(@NonNull final TextView textView, private static void setTextViewCharSequence(@NonNull final TextView textView,