/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.plaidapp.util;
import android.content.res.ColorStateList;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.text.Html;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.widget.TextView;
import in.uncod.android.bypass.Bypass;
import in.uncod.android.bypass.style.TouchableUrlSpan;
/**
* Utility methods for working with HTML.
*/
public class HtmlUtils {
private HtmlUtils() { }
/**
* Work around some 'features' of TextView and URLSpans. i.e. vanilla URLSpans do not react to
* touch so we replace them with our own {@link TouchableUrlSpan}
* & {@link LinkTouchMovementMethod} to fix this.
* <p/>
* Setting a custom MovementMethod on a TextView also alters touch handling (see
* TextView#fixFocusableAndClickableSettings) so we need to correct this.
*/
public static void setTextWithNiceLinks(TextView textView, CharSequence input) {
textView.setText(input);
textView.setMovementMethod(LinkTouchMovementMethod.getInstance());
textView.setFocusable(false);
textView.setClickable(false);
textView.setLongClickable(false);
}
/**
* Parse the given input using {@link TouchableUrlSpan}s rather than vanilla {@link URLSpan}s
* so that they respond to touch.
*/
public static SpannableStringBuilder parseHtml(
String input,
ColorStateList linkTextColor,
@ColorInt int linkHighlightColor) {
SpannableStringBuilder spanned = fromHtml(input);
// strip any trailing newlines
while (spanned.charAt(spanned.length() - 1) == '\n') {
spanned = spanned.delete(spanned.length() - 1, spanned.length());
}
return linkifyPlainLinks(spanned, linkTextColor, linkHighlightColor);
}
public static void parseAndSetText(TextView textView, String input) {
if (TextUtils.isEmpty(input)) return;
setTextWithNiceLinks(textView, parseHtml(input, textView.getLinkTextColors(),
textView.getHighlightColor()));
}
private static SpannableStringBuilder linkifyPlainLinks(
CharSequence input,
ColorStateList linkTextColor,
@ColorInt int linkHighlightColor) {
final SpannableString plainLinks = new SpannableString(input); // copy of input
// Linkify doesn't seem to work as expected on M+
// TODO: figure out why
//Linkify.addLinks(plainLinks, Linkify.WEB_URLS);
final URLSpan[] urlSpans = plainLinks.getSpans(0, plainLinks.length(), URLSpan.class);
// add any plain links to the output
final SpannableStringBuilder ssb = new SpannableStringBuilder(input);
for (URLSpan urlSpan : urlSpans) {
ssb.removeSpan(urlSpan);
ssb.setSpan(new TouchableUrlSpan(urlSpan.getURL(), linkTextColor, linkHighlightColor),
plainLinks.getSpanStart(urlSpan),
plainLinks.getSpanEnd(urlSpan),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return ssb;
}
private static SpannableStringBuilder fromHtml(String input) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return (SpannableStringBuilder) Html.fromHtml(input, Html.FROM_HTML_MODE_LEGACY);
} else {
return (SpannableStringBuilder) Html.fromHtml(input);
}
}
/**
* Parse Markdown and plain-text links.
* <p/>
* {@link Bypass} does not handle plain text links (i.e. not md syntax) and requires a
* {@code String} input (i.e. squashes any spans). {@link Linkify} handles plain links but also
* removes any existing spans. So we can't just run our input through both.
* <p/>
* Instead we use the markdown lib, then take a copy of the output and Linkify
* <strong>that</strong>. We then find any {@link URLSpan}s and add them to the markdown output.
* Best of both worlds.
*/
public static CharSequence parseMarkdownAndPlainLinks(
TextView textView,
String input,
Bypass markdown,
Bypass.LoadImageCallback loadImageCallback) {
CharSequence markedUp = markdown.markdownToSpannable(input, textView, loadImageCallback);
return linkifyPlainLinks(
markedUp, textView.getLinkTextColors(), textView.getHighlightColor());
}
/**
* Parse Markdown and plain-text links and set on the {@link TextView} with proper clickable
* spans.
*/
public static void parseMarkdownAndSetText(
TextView textView,
String input,
Bypass markdown,
Bypass.LoadImageCallback loadImageCallback) {
if (TextUtils.isEmpty(input)) return;
setTextWithNiceLinks(textView,
parseMarkdownAndPlainLinks(textView, input, markdown, loadImageCallback));
}
}