66import android .text .style .ClickableSpan ;
77import android .text .style .URLSpan ;
88import android .text .util .Linkify ;
9+ import android .util .Log ;
910import android .view .View ;
1011import android .widget .TextView ;
1112
13+ import androidx .annotation .NonNull ;
1214import androidx .core .text .HtmlCompat ;
1315
1416import io .noties .markwon .Markwon ;
1517import io .noties .markwon .linkify .LinkifyPlugin ;
18+ import io .reactivex .rxjava3 .android .schedulers .AndroidSchedulers ;
19+ import io .reactivex .rxjava3 .core .Single ;
20+ import io .reactivex .rxjava3 .disposables .Disposable ;
21+ import io .reactivex .rxjava3 .schedulers .Schedulers ;
1622
1723public final class TextLinkifier {
24+ public static final String TAG = TextLinkifier .class .getSimpleName ();
25+
1826 private TextLinkifier () {
1927 }
2028
@@ -23,40 +31,42 @@ private TextLinkifier() {
2331 * <p>
2432 * This will call
2533 * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
26- * after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
34+ * after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
2735 *
2836 * @param context the context to use
2937 * @param htmlBlock the htmlBlock to be linked
3038 * @param textView the TextView to set the htmlBlock linked
3139 * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
3240 * will be called
41+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
3342 */
34- public static void createLinksFromHtmlBlock (final Context context ,
35- final String htmlBlock ,
36- final TextView textView ,
37- final int htmlCompatFlag ) {
38- changeIntentsOfDescriptionLinks (context , HtmlCompat . fromHtml ( htmlBlock , htmlCompatFlag ) ,
39- textView );
43+ public static Disposable createLinksFromHtmlBlock (final Context context ,
44+ final String htmlBlock ,
45+ final TextView textView ,
46+ final int htmlCompatFlag ) {
47+ return changeIntentsOfDescriptionLinks (context ,
48+ HtmlCompat . fromHtml ( htmlBlock , htmlCompatFlag ), textView );
4049 }
4150
4251 /**
4352 * Create web links for contents with a plain text description.
4453 * <p>
4554 * This will call
4655 * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
47- * after linked the URLs with {@link TextView#setAutoLinkMask(int)} and
56+ * after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and
4857 * {@link TextView#setText(CharSequence, TextView.BufferType)}.
4958 *
5059 * @param context the context to use
5160 * @param plainTextBlock the block of plain text to be linked
5261 * @param textView the TextView to set the plain text block linked
62+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
5363 */
54- public static void createLinksFromPlainText (final Context context ,
55- final String plainTextBlock ,
56- final TextView textView ) {
64+ public static Disposable createLinksFromPlainText (final Context context ,
65+ final String plainTextBlock ,
66+ final TextView textView ) {
5767 textView .setAutoLinkMask (Linkify .WEB_URLS );
5868 textView .setText (plainTextBlock , TextView .BufferType .SPANNABLE );
59- changeIntentsOfDescriptionLinks (context , textView .getText (), textView );
69+ return changeIntentsOfDescriptionLinks (context , textView .getText (), textView );
6070 }
6171
6272 /**
@@ -70,48 +80,66 @@ public static void createLinksFromPlainText(final Context context,
7080 * @param context the context to use
7181 * @param markdownBlock the block of markdown text to be linked
7282 * @param textView the TextView to set the plain text block linked
83+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
7384 */
74- public static void createLinksFromMarkdownText (final Context context ,
75- final String markdownBlock ,
76- final TextView textView ) {
85+ public static Disposable createLinksFromMarkdownText (final Context context ,
86+ final String markdownBlock ,
87+ final TextView textView ) {
7788 final Markwon markwon = Markwon .builder (context ).usePlugin (LinkifyPlugin .create ()).build ();
7889 markwon .setMarkdown (textView , markdownBlock );
79- changeIntentsOfDescriptionLinks (context , textView .getText (), textView );
90+ return changeIntentsOfDescriptionLinks (context , textView .getText (), textView );
8091 }
8192
8293 /**
8394 * Change links generated by libraries in the description of a content to a custom link action.
8495 * <p>
85- * Instead of using an ACTION_VIEW intent in the description of a content, this method will
86- * parse the CharSequence and replace all current web links with
87- * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
96+ * Instead of using an {@link android.content.Intent# ACTION_VIEW} intent in the description of a
97+ * content, this method will parse the {@link CharSequence} and replace all current web links
98+ * with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
8899 * <p>
89- * This method is required in order to intercept links and maybe, show a confirmation dialog
100+ * This method is required in order to intercept links and e.g. show a confirmation dialog
90101 * before opening a web link.
91102 *
92103 * @param context the context to use
93104 * @param chars the CharSequence to be parsed
94105 * @param textView the TextView in which the converted CharSequence will be applied
106+ * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
95107 */
96- private static void changeIntentsOfDescriptionLinks (final Context context ,
97- final CharSequence chars ,
98- final TextView textView ) {
99- final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder (chars );
100- final URLSpan [] urls = textBlockLinked .getSpans (0 , chars .length (), URLSpan .class );
108+ private static Disposable changeIntentsOfDescriptionLinks (final Context context ,
109+ final CharSequence chars ,
110+ final TextView textView ) {
111+ return Single .fromCallable (() -> {
112+ final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder (chars );
113+ final URLSpan [] urls = textBlockLinked .getSpans (0 , chars .length (), URLSpan .class );
114+
115+ for (final URLSpan span : urls ) {
116+ final ClickableSpan clickableSpan = new ClickableSpan () {
117+ public void onClick (@ NonNull final View view ) {
118+ ShareUtils .openUrlInBrowser (context , span .getURL (), false );
119+ }
120+ };
101121
102- for (final URLSpan span : urls ) {
103- final ClickableSpan clickableSpan = new ClickableSpan () {
104- public void onClick (final View view ) {
105- ShareUtils .openUrlInBrowser (context , span .getURL (), false );
106- }
107- };
122+ textBlockLinked .setSpan (clickableSpan , textBlockLinked .getSpanStart (span ),
123+ textBlockLinked .getSpanEnd (span ), textBlockLinked .getSpanFlags (span ));
124+ textBlockLinked .removeSpan (span );
125+ }
108126
109- textBlockLinked .setSpan (clickableSpan , textBlockLinked .getSpanStart (span ),
110- textBlockLinked .getSpanEnd (span ), textBlockLinked .getSpanFlags (span ));
111- textBlockLinked .removeSpan (span );
112- }
127+ return textBlockLinked ;
128+ }).subscribeOn (Schedulers .computation ())
129+ .observeOn (AndroidSchedulers .mainThread ())
130+ .subscribe (
131+ textBlockLinked -> setTextViewCharSequence (textView , textBlockLinked ),
132+ throwable -> {
133+ Log .e (TAG , "Unable to linkify text" , throwable );
134+ // this should never happen, but if it does, just fallback to it
135+ setTextViewCharSequence (textView , chars );
136+ });
137+ }
113138
114- textView .setText (textBlockLinked );
139+ private static void setTextViewCharSequence (final TextView textView ,
140+ final CharSequence charSequence ) {
141+ textView .setText (charSequence );
115142 textView .setMovementMethod (LinkMovementMethod .getInstance ());
143+ textView .setVisibility (View .VISIBLE );
116144 }
117145}
0 commit comments