Java Code Examples for android.text.SpannableStringBuilder#insert()

The following examples show how to use android.text.SpannableStringBuilder#insert() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: TagHandlerImpl.java    From Markdown with MIT License 6 votes vote down vote up
@Override
public boolean em(Line line) {
    line = line.get();
    SpannableStringBuilder builder = (SpannableStringBuilder) line.getStyle();
    Matcher matcher = obtain(Tag.EM, builder);
    while (matcher.find()) {
        int start = matcher.start(1);
        int end = matcher.end(1);
        if (checkInCode(builder, start, end)) {
            continue;
        }
        SpannableStringBuilder sb = (SpannableStringBuilder) builder.subSequence(matcher.start(3), matcher.end(3));
        builder.delete(matcher.start(1), matcher.end(1));
        builder.insert(matcher.start(1), styleBuilder.em(sb));
        em(line);
        return true;
    }
    return false;
}
 
Example 2
Source File: MessageAdapter.java    From Pix-Art-Messenger with GNU General Public License v3.0 6 votes vote down vote up
private void applyQuoteSpan(SpannableStringBuilder body, int start, int end, boolean darkBackground) {
    if (start > 1 && !"\n\n".equals(body.subSequence(start - 2, start).toString())) {
        body.insert(start++, "\n");
        body.setSpan(new DividerSpan(false), start - 2, start, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        end++;
    }
    if (end < body.length() - 1 && !"\n\n".equals(body.subSequence(end, end + 2).toString())) {
        body.insert(end, "\n");
        body.setSpan(new DividerSpan(false), end, end + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    int color;
    if (activity.isOrangeTheme()) {
        color = darkBackground ? this.getMessageTextColor(darkBackground, false) : ContextCompat.getColor(activity, R.color.darkorange);
    } else {
        color = darkBackground ? this.getMessageTextColor(darkBackground, false) : ContextCompat.getColor(activity, R.color.darkblue);
    }
    final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
    body.setSpan(new QuoteSpan(color, metrics), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
 
Example 3
Source File: TagHandlerImpl.java    From Markdown with MIT License 6 votes vote down vote up
@Override
public boolean emItalic(Line line) {
    line = line.get();
    SpannableStringBuilder builder = (SpannableStringBuilder) line.getStyle();
    Matcher matcher = obtain(Tag.EM_ITALIC, builder);
    while (matcher.find()) {
        int start = matcher.start(1);
        int end = matcher.end(1);
        if (checkInCode(builder, start, end)) {
            continue;
        }
        SpannableStringBuilder sb = (SpannableStringBuilder) builder.subSequence(matcher.start(3), matcher.end(3));
        builder.delete(matcher.start(1), matcher.end(1));
        builder.insert(matcher.start(1), styleBuilder.emItalic(sb));
        emItalic(line);
        return true;
    }
    return false;
}
 
Example 4
Source File: GlobalGUIRoutines.java    From PhoneProfilesPlus with Apache License 2.0 6 votes vote down vote up
private static SpannableStringBuilder addNumbers(Spanned htmlSpanned, int numberFrom, int sp) {
    int listItemCount = numberFrom-1;
    SpannableStringBuilder spannableBuilder = new SpannableStringBuilder(htmlSpanned);
    BulletSpan[] spans = spannableBuilder.getSpans(0, spannableBuilder.length(), BulletSpan.class);
    if (spans != null) {
        for (BulletSpan span : spans) {
            int start = spannableBuilder.getSpanStart(span);
            int end  = spannableBuilder.getSpanEnd(span);
            spannableBuilder.removeSpan(span);
            ++listItemCount;
            spannableBuilder.insert(start, listItemCount + ". ");
            spannableBuilder.setSpan(new LeadingMarginSpan.Standard(0, sip(sp)), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        }
    }
    return spannableBuilder;
}
 
Example 5
Source File: TagHandlerImpl.java    From Markdown with MIT License 6 votes vote down vote up
@Override
public boolean image2(Line line) {
    line = line.get();
    SpannableStringBuilder builder = (SpannableStringBuilder) line.getStyle();
    Matcher matcher = obtain(Tag.IMAGE2, builder);
    if (matcher.find()) {
        String title = matcher.group(2);
        String id = matcher.group(3);
        Pair<String, String> image = idImageUrl.get(id);
        if (image != null) {
            builder.delete(matcher.start(1), matcher.end(1));
            builder.insert(matcher.start(1), styleBuilder.image(title, image.first, image.second));
        } else {
            return false;
        }
        image2(line);
        return true;
    }
    return false;
}
 
Example 6
Source File: TagHandlerImpl.java    From Markdown with MIT License 6 votes vote down vote up
@Override
public boolean link2(Line line) {
    line = line.get();
    SpannableStringBuilder builder = (SpannableStringBuilder) line.getStyle();
    Matcher matcher = obtain(Tag.LINK2, builder);
    if (matcher.find()) {
        String title = matcher.group(2);
        String id = matcher.group(3);
        Pair<String, String> link = idLinkLinks.get(id);
        if (link != null) {
            builder.delete(matcher.start(1), matcher.end(1));
            builder.insert(matcher.start(1), styleBuilder.link(title, link.first, link.second));
        } else {
            return false;
        }
        link2(line);
        return true;
    }
    return false;
}
 
Example 7
Source File: TagHandlerImpl.java    From Markdown with MIT License 6 votes vote down vote up
@Override
public boolean image(Line line) {
    line = line.get();
    SpannableStringBuilder builder = (SpannableStringBuilder) line.getStyle();
    Matcher matcher = obtain(Tag.IMAGE, builder);
    if (matcher.find()) {
        String title = matcher.group(2);
        String link = matcher.group(3);
        String hint = matcher.group(6);
        builder.delete(matcher.start(1), matcher.end(1));
        builder.insert(matcher.start(1), styleBuilder.image(title, link, hint));
        image(line);
        return true;
    }
    return false;
}
 
Example 8
Source File: TagHandlerImpl.java    From Markdown with MIT License 5 votes vote down vote up
@Override
public boolean autoLink(Line line) {
    line = line.get();
    SpannableStringBuilder builder = (SpannableStringBuilder) line.getStyle();
    Matcher matcher = obtain(Tag.AUTO_LINK, builder);
    boolean m = false;
    while (matcher.find()) {
        String content = matcher.group();
        builder.delete(matcher.start(), matcher.end());
        builder.insert(matcher.start(), styleBuilder.link(content, content, ""));
        m = true;
    }
    return m;
}
 
Example 9
Source File: SearchActivity.java    From droidkaigi2016 with Apache License 2.0 5 votes vote down vote up
private void bindText(TextView textView, SearchResult searchResult, String searchText) {
    String text = searchResult.text;
    if (TextUtils.isEmpty(text)) return;

    text = text.replace("\n", "  ");

    if (TextUtils.isEmpty(searchText)) {
        textView.setText(text);
    } else {
        int idx = text.toLowerCase().indexOf(searchText.toLowerCase());
        if (idx >= 0) {
            SpannableStringBuilder builder = new SpannableStringBuilder();
            builder.append(text);
            builder.setSpan(
                    textAppearanceSpan,
                    idx,
                    idx + searchText.length(),
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            );

            if (idx > ELLIPSIZE_LIMIT_COUNT && searchResult.isDescriptionType()) {
                builder.delete(0, idx - ELLIPSIZE_LIMIT_COUNT);
                builder.insert(0, ELLIPSIZE_TEXT);
            }

            textView.setText(builder);
        } else {
            textView.setText(text);
        }
    }
}
 
Example 10
Source File: RichEditText.java    From GSYRickText with MIT License 5 votes vote down vote up
/**
 * 插入表情
 *
 * @param name
 */
public void insertIcon(String name) {

    String curString = getText().toString();
    if ((curString.length() + name.length()) > richMaxLength) {
        return;
    }

    int resId = SmileUtils.getRedId(name);

    Drawable drawable = this.getResources().getDrawable(resId);
    if (drawable == null)
        return;
    drawable.setBounds(0, 0, richIconSize, richIconSize);//这里设置图片的大小
    ImageSpan imageSpan = new ImageSpan(drawable);
    SpannableString spannableString = new SpannableString(name);
    spannableString.setSpan(imageSpan, 0, spannableString.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);


    int index = Math.max(getSelectionStart(), 0);
    SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getText());
    spannableStringBuilder.insert(index, spannableString);

    setText(spannableStringBuilder);
    setSelection(index + spannableString.length());


}
 
Example 11
Source File: ContextBasedRangeFormattingCallback.java    From nextcloud-notes with GNU General Public License v3.0 5 votes vote down vote up
private void addMarkdown(SpannableStringBuilder ssb, int start, int end, String markdown, int typeface) {
    ssb.insert(end, markdown);
    ssb.insert(start, markdown);
    editText.getText().charAt(start);
    editText.getText().charAt(start + 1);
    ssb.setSpan(new StyleSpan(typeface), start, end + markdown.length() * 2, 1);
}
 
Example 12
Source File: JellyBeanSpanFixTextView.java    From html-textview with Apache License 2.0 5 votes vote down vote up
private FixingResult addSpacesAroundSpansUntilFixed(SpannableStringBuilder builder,
                                                    int widthMeasureSpec, int heightMeasureSpec) {

    Object[] spans = builder.getSpans(0, builder.length(), Object.class);
    List<Object> spansWithSpacesBefore = new ArrayList<>(spans.length);
    List<Object> spansWithSpacesAfter = new ArrayList<>(spans.length);

    for (Object span : spans) {
        int spanStart = builder.getSpanStart(span);
        if (isNotSpace(builder, spanStart - 1)) {
            builder.insert(spanStart, " ");
            spansWithSpacesBefore.add(span);
        }

        int spanEnd = builder.getSpanEnd(span);
        if (isNotSpace(builder, spanEnd)) {
            builder.insert(spanEnd, " ");
            spansWithSpacesAfter.add(span);
        }

        try {
            setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
            return FixingResult.fixed(spansWithSpacesBefore, spansWithSpacesAfter);
        } catch (IndexOutOfBoundsException ignored) {
        }
    }
    if (HtmlTextView.DEBUG) {
        Log.d(HtmlTextView.TAG, "Could not fix the Spanned by adding spaces around spans");
    }
    return FixingResult.notFixed();
}
 
Example 13
Source File: SmileUtils.java    From GSYRickText with MIT License 5 votes vote down vote up
/**
 * 文本转化表情处理
 *
 * @param editText  要显示的EditText
 * @param maxLength 最长高度
 * @param size      显示大小
 * @param name      需要转化的文本
 */
public static void insertIcon(EditText editText, int maxLength, int size, String name) {

    String curString = editText.toString();
    if ((curString.length() + name.length()) > maxLength) {
        return;
    }

    int resId = SmileUtils.getRedId(name);

    Drawable drawable = editText.getResources().getDrawable(resId);
    if (drawable == null)
        return;

    drawable.setBounds(0, 0, size, size);//这里设置图片的大小
    CenteredImageSpan CenteredImageSpan = new CenteredImageSpan(drawable);
    SpannableString spannableString = new SpannableString(name);
    spannableString.setSpan(CenteredImageSpan, 0, spannableString.length(), SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);


    int index = Math.max(editText.getSelectionStart(), 0);
    SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(editText.getText());
    spannableStringBuilder.insert(index, spannableString);

    editText.setText(spannableStringBuilder);
    editText.setSelection(index + spannableString.length());
}
 
Example 14
Source File: DefaultDecorator.java    From kaif-android with Apache License 2.0 5 votes vote down vote up
@Override
public void closeOrderedListItem(SpannableStringBuilder out) {
  OrderedList orderedList = getLast(out, OrderedList.class);
  if (orderedList != null) {
    int number = orderedList.getAndIncrement();
    int where = out.getSpanStart(getLast(out, OrderedListItem.class));
    out.insert(where, Integer.toString(number) + ". ");
  }
  //check BulletSpan2
  end(out, OrderedListItem.class, new LeadingMarginSpan.LeadingMarginSpan2.Standard(leading));
}
 
Example 15
Source File: MessageAdapter.java    From Conversations with GNU General Public License v3.0 5 votes vote down vote up
private void applyQuoteSpan(SpannableStringBuilder body, int start, int end, boolean darkBackground) {
    if (start > 1 && !"\n\n".equals(body.subSequence(start - 2, start).toString())) {
        body.insert(start++, "\n");
        body.setSpan(new DividerSpan(false), start - 2, start, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        end++;
    }
    if (end < body.length() - 1 && !"\n\n".equals(body.subSequence(end, end + 2).toString())) {
        body.insert(end, "\n");
        body.setSpan(new DividerSpan(false), end, end + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    int color = darkBackground ? this.getMessageTextColor(darkBackground, false)
            : ContextCompat.getColor(activity, R.color.green700_desaturated);
    DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
    body.setSpan(new QuoteSpan(color, metrics), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
 
Example 16
Source File: TagHandlerImpl.java    From Markdown with MIT License 5 votes vote down vote up
@Override
public boolean code(Line line) {
    line = line.get();
    SpannableStringBuilder builder = (SpannableStringBuilder) line.getStyle();
    Matcher matcher = obtain(Tag.CODE, builder);
    if (matcher.find()) {
        String content = matcher.group(3);
        builder.delete(matcher.start(1), matcher.end(1));
        builder.insert(matcher.start(1), styleBuilder.code(content));
        code(line);
        return true;
    }
    return false;
}
 
Example 17
Source File: RichEditText.java    From GSYRickText with MIT License 4 votes vote down vote up
/**
 * 处理at某人
 *
 * @param text      输入文本
 * @param spannable 处理过的文本
 * @param color     颜色
 * @param listUser  用户列表
 * @return Spannable
 */
private Spannable resolveAtInsert(String text, Spannable spannable, String color, List<UserModel> listUser) {

    if (listUser == null || listUser.size() <= 0) {
        return spannable;
    }

    //此处保存名字的键值
    Map<String, String> names = new HashMap<>();
    if (listUser.size() > 0) {
        for (UserModel userModel : listUser) {
            names.put(userModel.getUser_name(), userModel.getUser_name());
        }
    }
    int length = spannable.length();
    Pattern pattern = Pattern.compile("@[^\\s]+\\s?");
    Matcher matcher = pattern.matcher(spannable);
    SpannableStringBuilder spannableStringBuilder =
            new SpannableStringBuilder(spannable);
    for (int i = 0; i < length; i++) {
        if (matcher.find()) {
            String name = text.substring(matcher.start(), matcher.end());
            if (names.containsKey(name.replace("\b", "").replace(" ", ""))) {
                //直接用span会导致后面没文字的时候新输入的一起变色
                Spanned htmlText = Html.fromHtml(String.format("<font color='%s'>" + name + "</font>", color));
                spannableStringBuilder.replace(matcher.start(), matcher.start() + name.length(), htmlText);
                int index = matcher.start() + htmlText.length();
                if (index < text.length()) {
                    if (" ".equals(text.subSequence(index - 1, index))) {
                        spannableStringBuilder.replace(index - 1, index, "\b");
                    }
                } else {
                    if (text.substring(index - 1).equals(" ")) {
                        spannableStringBuilder.replace(index - 1, index, "\b");
                    } else {
                        //如果是最后面的没有空格,补上\b
                        spannableStringBuilder.insert(index, "\b");
                    }
                }
            }
        }
    }
    return spannableStringBuilder;
}
 
Example 18
Source File: UserLogAdapter.java    From 600SeriesAndroidUploader with MIT License 4 votes vote down vote up
private void setContent(TextView tv, UserLog ul) {

        if (!init) initDrawables(tv);

        SpannableStringBuilder ssb = new SpannableStringBuilder();

        UserLogMessage.TYPE type = UserLogMessage.TYPE.convert(ul.getType());
        String clock = FormatKit.getInstance().formatAsDayClockSeconds(ul.getTimestamp());
        String text = ul.getMessageParsed();

        if (largeText) clock += "\n";

        switch (type) {
            case WARN:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iWARN, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iWARN.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case HELP:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iHELP, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iHELP.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case INFO:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iINFO, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iINFO.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case NOTE:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iNOTE, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iNOTE.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case HISTORY:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iREFRESH, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iREFRESH.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case CGM:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iCGM, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iCGM.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case OPTION:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iSETTING, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iSETTING.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case STARTUP:
            case SHUTDOWN:
            case HEART:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iHEART, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(cHigh), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case SGV:
                ssb.append(" ").append(text);
                ssb.setSpan(new ForegroundColorSpan(cHigh), 1, text.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case ESTIMATE:
                ssb.append(" ").append(text);
                ssb.setSpan(new ForegroundColorSpan(cEestimate), 1, text.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case ISIG:
                ssb.append(" ").append(text);
                ssb.setSpan(new ForegroundColorSpan(cIsig), 1, text.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case REQUESTED:
            case RECEIVED:
                ssb.append(" ").append(text);
                ssb.setSpan(new ForegroundColorSpan(cHistory), 1, text.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case SHARE:
                ssb.append(" * ").append(text);
                ssb.setSpan(new ImageSpan(iSHARE, DynamicDrawableSpan.ALIGN_BASELINE), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                ssb.setSpan(new ForegroundColorSpan(iSHARE.getColor()), 3, text.length() + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            case PUSHOVER:
                ssb.append(" ").append(text);
                ssb.setSpan(new ForegroundColorSpan(cPushover), 1, text.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                break;
            default:
                ssb.append(" ").append(text);
                ssb.setSpan(new ForegroundColorSpan(cNormal), 1, text.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        ssb.insert(0, clock);
        ssb.setSpan(new ForegroundColorSpan(cLow), 0, clock.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        ssb.setSpan(new RelativeSizeSpan(0.78f), 0, clock.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        tv.setText(ssb);
    }
 
Example 19
Source File: FeedEditText.java    From umeng_community_android with MIT License 4 votes vote down vote up
/**
 * 封topic的TextView
 * 
 * @param str
 * @return
 */
public void insertTopics(List<Topic> topics) {

    if (topics == null || topics.size() == 0) {
        return;
    }
    // 光标的位置
    int editSection = getSelectionStart();

    Log.d("", "### insertTopicText, start = " + editSection);
    SpannableStringBuilder ssb = new SpannableStringBuilder(getText());

    int count = topics.size();
    for (int i = 0; i < count; i++) {
        final Topic topicItem = topics.get(i);
        final String name = topicItem.name + " ";
        // 如果已经有了该好友,则直接返回
        if (mTopicMap.containsValue(topicItem)) {
            continue;
        }
        isDecorating = true;
        int ssbLength = ssb.length();
        if (editSection > ssbLength) {
            editSection = ssbLength;
        }
        ssb.insert(editSection, name);
        setText(ssb);
        // 更新map索引
        updateMapIndex(editSection, name.length());
        Log.d("#####", "#####put start : " + editSection);
        // 将所有at的位置存储在map中
        mTopicMap.put(editSection, topicItem);
        // 更新起始点
        editSection += name.length();
    }

    // 包装文本
    decorateText();
    isDecorating = false;
    setSelection(editSection);
    mCursorIndex = editSection;
}
 
Example 20
Source File: MessageAdapter.java    From Pix-Art-Messenger with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Applies QuoteSpan to group of lines which starts with > or » characters.
 * Appends likebreaks and applies DividerSpan to them to show a padding between quote and text.
 */
private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) {
    boolean startsWithQuote = false;
    char previous = '\n';
    int lineStart = -1;
    int lineTextStart = -1;
    int quoteStart = -1;
    for (int i = 0; i <= body.length(); i++) {
        char current = body.length() > i ? body.charAt(i) : '\n';
        if (lineStart == -1) {
            if (previous == '\n') {
                if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body, i))
                        || current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i)) {
                    // Line start with quote
                    lineStart = i;
                    if (quoteStart == -1) quoteStart = i;
                    if (i == 0) startsWithQuote = true;
                } else if (quoteStart >= 0) {
                    // Line start without quote, apply spans there
                    applyQuoteSpan(body, quoteStart, i - 1, darkBackground);
                    quoteStart = -1;
                }
            }
        } else {
            // Remove extra spaces between > and first character in the line
            // > character will be removed too
            if (current != ' ' && lineTextStart == -1) {
                lineTextStart = i;
            }
            if (current == '\n') {
                body.delete(lineStart, lineTextStart);
                i -= lineTextStart - lineStart;
                if (i == lineStart) {
                    // Avoid empty lines because span over empty line can be hidden
                    body.insert(i++, " ");
                }
                lineStart = -1;
                lineTextStart = -1;
            }
        }
        previous = current;
    }
    if (quoteStart >= 0) {
        // Apply spans to finishing open quote
        applyQuoteSpan(body, quoteStart, body.length(), darkBackground);
    }
    return startsWithQuote;
}