package me.ccrama.redditslide;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.BackgroundColorSpan;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import com.cocosw.bottomsheet.BottomSheet;
import com.devspark.robototextview.widget.RobotoTextView;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.ccrama.redditslide.Activities.Album;
import me.ccrama.redditslide.Activities.AlbumPager;
import me.ccrama.redditslide.Activities.MediaView;
import me.ccrama.redditslide.Activities.TumblrPager;
import me.ccrama.redditslide.ForceTouch.PeekView;
import me.ccrama.redditslide.ForceTouch.PeekViewActivity;
import me.ccrama.redditslide.ForceTouch.builder.Peek;
import me.ccrama.redditslide.ForceTouch.builder.PeekViewOptions;
import me.ccrama.redditslide.ForceTouch.callback.OnButtonUp;
import me.ccrama.redditslide.ForceTouch.callback.OnPop;
import me.ccrama.redditslide.ForceTouch.callback.OnRemove;
import me.ccrama.redditslide.ForceTouch.callback.SimpleOnPeek;
import me.ccrama.redditslide.Views.CustomQuoteSpan;
import me.ccrama.redditslide.Views.PeekMediaView;
import me.ccrama.redditslide.Visuals.Palette;
import me.ccrama.redditslide.handler.TextViewLinkHandler;
import me.ccrama.redditslide.util.LinkUtil;
import me.ccrama.redditslide.util.LogUtil;
/**
* Created by carlo_000 on 1/11/2016.
*/
public class SpoilerRobotoTextView extends RobotoTextView implements ClickableText {
private List<CharacterStyle> storedSpoilerSpans = new ArrayList<>();
private List<Integer> storedSpoilerStarts = new ArrayList<>();
private List<Integer> storedSpoilerEnds = new ArrayList<>();
private static final Pattern htmlSpoilerPattern =
Pattern.compile("<a href=\"([#/](?:spoiler|sp|s))\">([^<]*)</a>");
public SpoilerRobotoTextView(Context context) {
super(context);
setLineSpacing(0, 1.1f);
}
public SpoilerRobotoTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setLineSpacing(0, 1.1f);
}
public SpoilerRobotoTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setLineSpacing(0, 1.1f);
}
public boolean isSpoilerClicked() {
return spoilerClicked;
}
public void resetSpoilerClicked() {
spoilerClicked = false;
}
public boolean spoilerClicked = false;
private static SpannableStringBuilder removeNewlines(SpannableStringBuilder s) {
int start = 0;
int end = s.length();
while (start < end && Character.isWhitespace(s.charAt(start))) {
start++;
}
while (end > start && Character.isWhitespace(s.charAt(end - 1))) {
end--;
}
return (SpannableStringBuilder) s.subSequence(start, end);
}
/**
* Set the text from html. Handles formatting spoilers, links etc. <p/> The text must be valid
* html.
*
* @param text html text
*/
public void setTextHtml(CharSequence text) {
setTextHtml(text, "");
}
/**
* Set the text from html. Handles formatting spoilers, links etc. <p/> The text must be valid
* html.
*
* @param baseText html text
* @param subreddit the subreddit to theme
*/
public void setTextHtml(CharSequence baseText, String subreddit) {
String text = wrapAlternateSpoilers(saveEmotesFromDestruction(baseText.toString().trim()));
SpannableStringBuilder builder = (SpannableStringBuilder) Html.fromHtml(text);
replaceQuoteSpans(
builder); //replace the <blockquote> blue line with something more colorful
if (text.contains("<a")) {
setEmoteSpans(builder); //for emote enabled subreddits
}
if (text.contains("[")) {
setCodeFont(builder);
setSpoilerStyle(builder, subreddit);
}
if (text.contains("[[d[")) {
setStrikethrough(builder);
}
if (text.contains("[[h[")) {
setHighlight(builder, subreddit);
}
if (subreddit != null && !subreddit.isEmpty()) {
setMovementMethod(new TextViewLinkHandler(this, subreddit, builder));
setFocusable(false);
setClickable(false);
if (subreddit.equals("FORCE_LINK_CLICK")) {
setLongClickable(false);
}
}
builder = removeNewlines(builder);
builder.append(" ");
super.setText(builder, BufferType.SPANNABLE);
}
/**
* Replaces the blue line produced by <blockquote>s with something more visible
*
* @param spannable parsed comment text #fromHtml
*/
private void replaceQuoteSpans(Spannable spannable) {
QuoteSpan[] quoteSpans = spannable.getSpans(0, spannable.length(), QuoteSpan.class);
for (QuoteSpan quoteSpan : quoteSpans) {
final int start = spannable.getSpanStart(quoteSpan);
final int end = spannable.getSpanEnd(quoteSpan);
final int flags = spannable.getSpanFlags(quoteSpan);
spannable.removeSpan(quoteSpan);
//If the theme is Light or Sepia, use a darker blue; otherwise, use a lighter blue
final int barColor =
(SettingValues.currentTheme == 1 || SettingValues.currentTheme == 5)
? ContextCompat.getColor(getContext(), R.color.md_blue_600)
: ContextCompat.getColor(getContext(), R.color.md_blue_400);
final int BAR_WIDTH = 4;
final int GAP = 5;
spannable.setSpan(new CustomQuoteSpan(Color.TRANSPARENT, //background color
barColor, //bar color
BAR_WIDTH, //bar width
GAP), //bar + text gap
start, end, flags);
}
}
private String wrapAlternateSpoilers(String html) {
Matcher htmlSpoilerMatcher = htmlSpoilerPattern.matcher(html);
while (htmlSpoilerMatcher.find()) {
String newPiece = htmlSpoilerMatcher.group();
String inner = "<a href=\"/spoiler\">spoiler< [[s[ "
+ newPiece.substring(newPiece.indexOf(">") + 1,
newPiece.indexOf("<", newPiece.indexOf(">")))
+ "]s]]</a>";
html = html.replace(htmlSpoilerMatcher.group(), inner);
}
return html;
}
private String saveEmotesFromDestruction(String html) {
//Emotes often have no spoiler caption, and therefore are converted to empty anchors. Html.fromHtml removes anchors with zero length node text. Find zero length anchors that start with "/" and add "." to them.
Pattern htmlEmotePattern = Pattern.compile("<a href=\"/.*\"></a>");
Matcher htmlEmoteMatcher = htmlEmotePattern.matcher(html);
while (htmlEmoteMatcher.find()) {
String newPiece = htmlEmoteMatcher.group();
//Ignore empty tags marked with sp.
if (!htmlEmoteMatcher.group().contains("href=\"/sp\"")) {
newPiece = newPiece.replace("></a", ">.</a");
html = html.replace(htmlEmoteMatcher.group(), newPiece);
}
}
return html;
}
private void setEmoteSpans(SpannableStringBuilder builder) {
for (URLSpan span : builder.getSpans(0, builder.length(), URLSpan.class)) {
if (SettingValues.typeInText) {
setLinkTypes(builder, span);
}
if (SettingValues.largeLinks) {
setLargeLinks(builder, span);
}
File emoteDir = new File(Environment.getExternalStorageDirectory(), "RedditEmotes");
File emoteFile = new File(emoteDir, span.getURL().replace("/", "").replaceAll("-.*", "")
+ ".png"); //BPM uses "-" to add dynamics for emotes in browser. Fall back to original here if exists.
boolean startsWithSlash = span.getURL().startsWith("/");
boolean hasOnlyOneSlash = StringUtils.countMatches(span.getURL(), "/") == 1;
if (emoteDir.exists() && startsWithSlash && hasOnlyOneSlash && emoteFile.exists()) {
//We've got an emote match
int start = builder.getSpanStart(span);
int end = builder.getSpanEnd(span);
CharSequence textCovers = builder.subSequence(start, end);
//Make sure bitmap loaded works well with screen density.
BitmapFactory.Options options = new BitmapFactory.Options();
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
options.inDensity = 240;
options.inScreenDensity = metrics.densityDpi;
options.inScaled = true;
//Since emotes are not directly attached to included text, add extra character to attach image to.
builder.removeSpan(span);
if (builder.subSequence(start, end).charAt(0) != '.') {
builder.insert(start, ".");
}
Bitmap emoteBitmap = BitmapFactory.decodeFile(emoteFile.getAbsolutePath(), options);
builder.setSpan(new ImageSpan(getContext(), emoteBitmap), start, start + 1,
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
//Check if url span has length. If it does, it's a spoiler/caption
if (textCovers.length() > 1) {
builder.setSpan(new URLSpan("/sp"), start + 1, end + 1,
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
builder.setSpan(new StyleSpan(Typeface.ITALIC), start + 1, end + 1,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
builder.append("\n"); //Newline to fix text wrapping issues
}
}
}
private void setLinkTypes(SpannableStringBuilder builder, URLSpan span) {
String url = span.getURL();
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
String text = builder.subSequence(builder.getSpanStart(span), builder.getSpanEnd(span))
.toString();
if (!text.equalsIgnoreCase(url)) {
ContentType.Type contentType = ContentType.getContentType(url);
String bod;
try {
bod = " (" + ((url.contains("/") && url.startsWith("/") && !(url.split("/").length
> 2)) ? url
: (getContext().getString(ContentType.getContentID(contentType, false)) + (
contentType == ContentType.Type.LINK ? " " + Uri.parse(url)
.getHost() : ""))) + ")";
} catch (Exception e) {
bod = " ("
+ getContext().getString(ContentType.getContentID(contentType, false))
+ ")";
}
SpannableStringBuilder b = new SpannableStringBuilder(bod);
b.setSpan(new StyleSpan(Typeface.BOLD), 0, b.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
b.setSpan(new RelativeSizeSpan(0.8f), 0, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
builder.insert(builder.getSpanEnd(span), b);
}
}
private void setLargeLinks(SpannableStringBuilder builder, URLSpan span) {
builder.setSpan(new RelativeSizeSpan(1.3f), builder.getSpanStart(span),
builder.getSpanEnd(span), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private void setStrikethrough(SpannableStringBuilder builder) {
final int offset = "[[d[".length(); // == "]d]]".length()
int start = -1;
int end;
for (int i = 0; i < builder.length() - 3; i++) {
if (builder.charAt(i) == '['
&& builder.charAt(i + 1) == '['
&& builder.charAt(i + 2) == 'd'
&& builder.charAt(i + 3) == '[') {
start = i + offset;
} else if (builder.charAt(i) == ']'
&& builder.charAt(i + 1) == 'd'
&& builder.charAt(i + 2) == ']'
&& builder.charAt(i + 3) == ']') {
end = i;
builder.setSpan(new StrikethroughSpan(), start, end,
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
builder.delete(end, end + offset);
builder.delete(start - offset, start);
i -= offset + (end - start); // length of text
}
}
}
private void setHighlight(SpannableStringBuilder builder, String subreddit) {
final int offset = "[[h[".length(); // == "]h]]".length()
int start = -1;
int end;
for (int i = 0; i < builder.length() - 4; i++) {
if (builder.charAt(i) == '['
&& builder.charAt(i + 1) == '['
&& builder.charAt(i + 2) == 'h'
&& builder.charAt(i + 3) == '[') {
start = i + offset;
} else if (builder.charAt(i) == ']'
&& builder.charAt(i + 1) == 'h'
&& builder.charAt(i + 2) == ']'
&& builder.charAt(i + 3) == ']') {
end = i;
builder.setSpan(new BackgroundColorSpan(Palette.getColor(subreddit)), start, end,
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
builder.delete(end, end + offset);
builder.delete(start - offset, start);
i -= offset + (end - start); // length of text
}
}
}
@Override
public void onLinkClick(String url, int xOffset, String subreddit, URLSpan span) {
if (url == null) {
((View) getParent()).callOnClick();
return;
}
ContentType.Type type = ContentType.getContentType(url);
Context context = getContext();
Activity activity = null;
if (context instanceof Activity) {
activity = (Activity) context;
} else if (context instanceof android.support.v7.view.ContextThemeWrapper) {
activity =
(Activity) ((android.support.v7.view.ContextThemeWrapper) context).getBaseContext();
} else if (context instanceof ContextWrapper) {
Context context1 = ((ContextWrapper) context).getBaseContext();
if (context1 instanceof Activity) {
activity = (Activity) context1;
} else if (context1 instanceof ContextWrapper) {
Context context2 = ((ContextWrapper) context1).getBaseContext();
if (context2 instanceof Activity) {
activity = (Activity) context2;
} else if (context2 instanceof ContextWrapper) {
activity =
(Activity) ((android.support.v7.view.ContextThemeWrapper) context2).getBaseContext();
}
}
} else {
throw new RuntimeException("Could not find activity from context:" + context);
}
if (!PostMatch.openExternal(url) || type == ContentType.Type.VIDEO) {
switch (type) {
case DEVIANTART:
case IMGUR:
case XKCD:
if (SettingValues.image) {
Intent intent2 = new Intent(activity, MediaView.class);
intent2.putExtra(MediaView.EXTRA_URL, url);
intent2.putExtra(MediaView.SUBREDDIT, subreddit);
activity.startActivity(intent2);
} else {
Reddit.defaultShare(url, activity);
}
break;
case REDDIT:
new OpenRedditLink(activity, url);
break;
case LINK:
LogUtil.v("Opening link");
LinkUtil.openUrl(url, Palette.getColor(subreddit), activity);
break;
case SELF:
break;
case STREAMABLE:
case VID_ME:
openStreamable(url, subreddit);
break;
case ALBUM:
if (SettingValues.album) {
if (SettingValues.albumSwipe) {
Intent i = new Intent(activity, AlbumPager.class);
i.putExtra(Album.EXTRA_URL, url);
i.putExtra(AlbumPager.SUBREDDIT, subreddit);
activity.startActivity(i);
} else {
Intent i = new Intent(activity, Album.class);
i.putExtra(Album.SUBREDDIT, subreddit);
i.putExtra(Album.EXTRA_URL, url);
activity.startActivity(i);
}
} else {
Reddit.defaultShare(url, activity);
}
break;
case TUMBLR:
if (SettingValues.image) {
if (SettingValues.albumSwipe) {
Intent i = new Intent(activity, TumblrPager.class);
i.putExtra(Album.EXTRA_URL, url);
activity.startActivity(i);
} else {
Intent i = new Intent(activity, TumblrPager.class);
i.putExtra(Album.EXTRA_URL, url);
activity.startActivity(i);
}
} else {
Reddit.defaultShare(url, activity);
}
break;
case IMAGE:
openImage(url, subreddit);
break;
case GIF:
openGif(url, subreddit);
break;
case NONE:
break;
case VIDEO:
if (Reddit.videoPlugin) {
try {
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setClassName("ccrama.me.slideyoutubeplugin",
"ccrama.me.slideyoutubeplugin.YouTubeView");
sharingIntent.putExtra("url", url);
activity.startActivity(sharingIntent);
} catch (Exception e) {
Reddit.defaultShare(url, activity);
}
} else {
Reddit.defaultShare(url, activity);
}
case SPOILER:
spoilerClicked = true;
setOrRemoveSpoilerSpans(xOffset, span);
break;
case EXTERNAL:
Reddit.defaultShare(url, activity);
break;
}
} else {
Reddit.defaultShare(url, context);
}
}
@Override
public void onLinkLongClick(final String baseUrl, MotionEvent event) {
if (baseUrl == null) {
return;
}
final String url = StringEscapeUtils.unescapeHtml4(baseUrl);
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Activity activity = null;
final Context context = getContext();
if (context instanceof Activity) {
activity = (Activity) context;
} else if (context instanceof android.support.v7.view.ContextThemeWrapper) {
activity =
(Activity) ((android.support.v7.view.ContextThemeWrapper) context).getBaseContext();
} else if (context instanceof ContextWrapper) {
Context context1 = ((ContextWrapper) context).getBaseContext();
if (context1 instanceof Activity) {
activity = (Activity) context1;
} else if (context1 instanceof ContextWrapper) {
Context context2 = ((ContextWrapper) context1).getBaseContext();
if (context2 instanceof Activity) {
activity = (Activity) context2;
} else if (context2 instanceof ContextWrapper) {
activity =
(Activity) ((android.support.v7.view.ContextThemeWrapper) context2).getBaseContext();
}
}
} else {
throw new RuntimeException("Could not find activity from context:" + context);
}
if (activity != null && !activity.isFinishing()) {
if (SettingValues.peek) {
Peek.into(R.layout.peek_view, new SimpleOnPeek() {
@Override
public void onInflated(final PeekView peekView, final View rootView) {
//do stuff
TextView text = ((TextView) rootView.findViewById(R.id.title));
text.setText(url);
text.setTextColor(Color.WHITE);
((PeekMediaView) rootView.findViewById(R.id.peek)).setUrl(url);
peekView.addButton((R.id.copy), new OnButtonUp() {
@Override
public void onButtonUp() {
ClipboardManager clipboard =
(ClipboardManager) rootView.getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Link", url);
clipboard.setPrimaryClip(clip);
Toast.makeText(rootView.getContext(),
R.string.submission_link_copied, Toast.LENGTH_SHORT).show();
}
});
peekView.setOnRemoveListener(new OnRemove() {
@Override
public void onRemove() {
((PeekMediaView) rootView.findViewById(R.id.peek)).doClose();
}
});
peekView.addButton((R.id.share), new OnButtonUp() {
@Override
public void onButtonUp() {
Reddit.defaultShareText("", url, rootView.getContext());
}
});
peekView.addButton((R.id.pop), new OnButtonUp() {
@Override
public void onButtonUp() {
Reddit.defaultShareText("", url, rootView.getContext());
}
});
peekView.addButton((R.id.external), new OnButtonUp() {
@Override
public void onButtonUp() {
LinkUtil.openExternally(url, context, false);
}
});
peekView.setOnPop(new OnPop() {
@Override
public void onPop() {
onLinkClick(url, 0, "", null);
}
});
}
})
.with(new PeekViewOptions().setFullScreenPeek(true))
.show((PeekViewActivity) activity, event);
} else {
BottomSheet.Builder b = new BottomSheet.Builder(activity).title(url).grid();
int[] attrs = new int[]{R.attr.tint};
TypedArray ta = getContext().obtainStyledAttributes(attrs);
int color = ta.getColor(0, Color.WHITE);
Drawable open = getResources().getDrawable(R.drawable.ic_open_in_browser);
open.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
Drawable share = getResources().getDrawable(R.drawable.ic_share);
share.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
Drawable copy = getResources().getDrawable(R.drawable.ic_content_copy);
copy.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
ta.recycle();
b.sheet(R.id.open_link, open,
getResources().getString(R.string.submission_link_extern));
b.sheet(R.id.share_link, share, getResources().getString(R.string.share_link));
b.sheet(R.id.copy_link, copy,
getResources().getString(R.string.submission_link_copy));
final Activity finalActivity = activity;
b.listener(new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case R.id.open_link:
LinkUtil.openExternally(url, context, false);
break;
case R.id.share_link:
Reddit.defaultShareText("", url, finalActivity);
break;
case R.id.copy_link:
ClipboardManager clipboard =
(ClipboardManager) finalActivity.getSystemService(
Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Link", url);
clipboard.setPrimaryClip(clip);
Toast.makeText(finalActivity, R.string.submission_link_copied,
Toast.LENGTH_SHORT).show();
break;
}
}
}).show();
}
}
}
private void openGif(String url, String subreddit) {
if (SettingValues.gif) {
Intent myIntent = new Intent(getContext(), MediaView.class);
myIntent.putExtra(MediaView.EXTRA_URL, url);
myIntent.putExtra(MediaView.SUBREDDIT, subreddit);
getContext().startActivity(myIntent);
} else {
Reddit.defaultShare(url, getContext());
}
}
private void openStreamable(String url, String subreddit) {
if (SettingValues.video) { //todo maybe streamable here?
Intent myIntent = new Intent(getContext(), MediaView.class);
myIntent.putExtra(MediaView.EXTRA_URL, url);
myIntent.putExtra(MediaView.SUBREDDIT, subreddit);
getContext().startActivity(myIntent);
} else {
Reddit.defaultShare(url, getContext());
}
}
private void openImage(String submission, String subreddit) {
if (SettingValues.image) {
Intent myIntent = new Intent(getContext(), MediaView.class);
myIntent.putExtra(MediaView.EXTRA_URL, submission);
myIntent.putExtra(MediaView.SUBREDDIT, subreddit);
getContext().startActivity(myIntent);
} else {
Reddit.defaultShare(submission, getContext());
}
}
public void setOrRemoveSpoilerSpans(int endOfLink, URLSpan span) {
if (span != null) {
int offset = (span.getURL().contains("hidden")) ? -1 : 2;
Spannable text = (Spannable) getText();
// add 2 to end of link since there is a white space between the link text and the spoiler
ForegroundColorSpan[] foregroundColors =
text.getSpans(endOfLink + offset, endOfLink + offset,
ForegroundColorSpan.class);
if (foregroundColors.length > 1) {
text.removeSpan(foregroundColors[1]);
setText(text);
} else {
for (int i = 1; i < storedSpoilerStarts.size(); i++) {
if (storedSpoilerStarts.get(i) < endOfLink + offset
&& storedSpoilerEnds.get(i) > endOfLink + offset) {
try {
text.setSpan(storedSpoilerSpans.get(i), storedSpoilerStarts.get(i),
storedSpoilerEnds.get(i) > text.toString().length() ?
storedSpoilerEnds.get(i)
+ offset : storedSpoilerEnds.get(i),
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
} catch (Exception ignored) {
//catch out of bounds
ignored.printStackTrace();
}
}
}
setText(text);
}
}
}
/**
* Set the necessary spans for each spoiler. <p/> The algorithm works in the same way as
* <code>setCodeFont</code>.
*
* @param sequence
* @return
*/
private CharSequence setSpoilerStyle(SpannableStringBuilder sequence, String subreddit) {
int start = 0;
int end = 0;
for (int i = 0; i < sequence.length(); i++) {
if (sequence.charAt(i) == '[' && i < sequence.length() - 3) {
if (sequence.charAt(i + 1) == '['
&& sequence.charAt(i + 2) == 's'
&& sequence.charAt(i + 3) == '[') {
start = i;
}
} else if (sequence.charAt(i) == ']' && i < sequence.length() - 3) {
if (sequence.charAt(i + 1) == 's'
&& sequence.charAt(i + 2) == ']'
&& sequence.charAt(i + 3) == ']') {
end = i;
}
}
if (end > start) {
sequence.delete(end, end + 4);
sequence.delete(start, start + 4);
BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(
Palette.getDarkerColor(Palette.getColor(subreddit)));
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(
Palette.getDarkerColor(Palette.getColor(subreddit)));
ForegroundColorSpan underneathColorSpan = new ForegroundColorSpan(Color.WHITE);
URLSpan urlSpan = sequence.getSpans(start, start, URLSpan.class)[0];
sequence.setSpan(urlSpan, sequence.getSpanStart(urlSpan), start - 1,
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
sequence.setSpan(new URLSpanNoUnderline("#spoilerhidden"), start, end - 4,
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
// spoiler text has a space at the front
sequence.setSpan(backgroundColorSpan, start + 1, end - 4,
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
sequence.setSpan(underneathColorSpan, start, end - 4,
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
sequence.setSpan(foregroundColorSpan, start, end - 4,
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
storedSpoilerSpans.add(underneathColorSpan);
storedSpoilerSpans.add(foregroundColorSpan);
storedSpoilerSpans.add(backgroundColorSpan);
// Shift 1 to account for remove of beginning "<"
storedSpoilerStarts.add(start - 1);
storedSpoilerStarts.add(start - 1);
storedSpoilerStarts.add(start - 1);
storedSpoilerEnds.add(end - 5);
storedSpoilerEnds.add(end - 5);
storedSpoilerEnds.add(end - 5);
sequence.delete(start - 2, start - 1); // remove the trailing <
start = 0;
end = 0;
i = i - 5; // move back to compensate for removal of [[s[
}
}
return sequence;
}
private class URLSpanNoUnderline extends URLSpan {
public URLSpanNoUnderline(String url) {
super(url);
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
}
/**
* Sets the styling for string with code segments. <p/> The general process is to search for
* <code>[[<[</code> and <code>]>]]</code> tokens to find the code fragments within the
* escaped text. A <code>Spannable</code> is created which which breaks up the origin sequence
* into non-code and code fragments, and applies a monospace font to the code fragments.
*
* @param sequence the Spannable generated from Html.fromHtml
* @return the message with monospace font applied to code fragments
*/
private SpannableStringBuilder setCodeFont(SpannableStringBuilder sequence) {
int start = 0;
int end = 0;
for (int i = 0; i < sequence.length(); i++) {
if (sequence.charAt(i) == '[' && i < sequence.length() - 3) {
if (sequence.charAt(i + 1) == '['
&& sequence.charAt(i + 2) == '<'
&& sequence.charAt(i + 3) == '[') {
start = i;
}
} else if (sequence.charAt(i) == ']' && i < sequence.length() - 3) {
if (sequence.charAt(i + 1) == '>'
&& sequence.charAt(i + 2) == ']'
&& sequence.charAt(i + 3) == ']') {
end = i;
}
}
if (end > start) {
sequence.delete(end, end + 4);
sequence.delete(start, start + 4);
sequence.setSpan(new TypefaceSpan("monospace"), start, end - 4,
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
start = 0;
end = 0;
i = i - 4; // move back to compensate for removal of [[<[
}
}
return sequence;
}
}