/** * Wire * Copyright (C) 2016 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.waz.zclient.ui.utils; import android.content.Context; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.TypefaceSpan; import android.util.Pair; import android.view.View; import android.widget.TextView; import com.waz.zclient.ui.R; import timber.log.Timber; import java.util.ArrayList; import java.util.List; public class TextViewUtils { private TextViewUtils() { } /** * Will highlight a single section of a TextView between 2 "_" marks. * For example, in "Click _HERE_ please." "HERE" will be made the color specified and bolded. * @param textView * @param highlightColor * * CAUTION: This does not work together with textAllCaps attribute! */ public static void highlightAndBoldText(TextView textView, int highlightColor) { final String string = textView.getText().toString(); textView.setText(getHighlightText(textView.getContext(), string, highlightColor, true)); } public static CharSequence getHighlightText(Context context, String string, int highlightColor, boolean bold) { final int highlightStart = string.indexOf('_'); if (highlightStart < 0) { Timber.e("Failed to highlight text - could not find _ marker in string."); return string; } final int highlightEnd = string.lastIndexOf('_'); if (highlightStart >= highlightEnd) { Timber.e("Failed to highlight text - make sure you have 2 _ markers to denote start and end of highlight region"); return string; } StringBuilder stringBuilder = new StringBuilder(string.substring(0, highlightStart)); stringBuilder.append(string.substring(highlightStart + 1, highlightEnd)); if (highlightEnd < string.length() - 1) { stringBuilder.append(string.substring(highlightEnd + 1, string.length())); } SpannableString colorSpannable = new SpannableString(stringBuilder.toString()); colorSpannable.setSpan(new ForegroundColorSpan(highlightColor), highlightStart, highlightEnd - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if (bold) { colorSpannable.setSpan(new CustomTypefaceSpan("", context.getResources().getString(R.string.wire__typeface__bold)), highlightStart, highlightEnd - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return colorSpannable; } /** * Will bold all sections of the text in a TextView which have [[ ]] around them. * For example "Here [[we]] are [[now]]." would bold "we" and "now". * @param textView * * CAUTION: This does not work together with textAllCaps attribute! */ public static void boldText(TextView textView) { highlightText(textView, R.string.wire__typeface__bold); } public static CharSequence getBoldText(Context context, String text) { return getTypefaceHighlightText(context, text, R.string.wire__typeface__bold, null, 0, 0); } public static CharSequence getBoldHighlightText(Context context, String text, int highlightColor, int highlightStart, int highlightEnd) { return getTypefaceHighlightText(context, text, R.string.wire__typeface__bold, highlightColor, highlightStart, highlightEnd); } /** * Will make all sections of the text in a TextView which have [[ ]] around them medium font. * For example "Here [[we]] are [[now]]." would make "we" and "now" medium. * @param textView * * CAUTION: This does not work together with textAllCaps attribute! */ public static void mediumText(TextView textView) { highlightText(textView, R.string.wire__typeface__regular); } /** * Will highlight all sections with the desired {@param typefaceRes} of the text in a TextView which have [[ ]] around them. * For example "Here [[we]] are [[now]]." would highlight "we" and "now". * @param textView * * CAUTION: This does not work together with textAllCaps attribute! */ public static void highlightText(TextView textView, @StringRes int typefaceRes) { String string = textView.getText().toString(); textView.setText(getTypefaceHighlightText(textView.getContext(), string, typefaceRes, null, 0, 0)); } private static CharSequence getTypefaceHighlightText(Context context, String string, @StringRes int typefaceRes, Integer highlightColor, int colorHighlightStart, int colorHighlightEnd) { List<Pair<Integer, Integer>> spanPositions = new ArrayList<>(); int highlightStart; int highlightEnd = 0; while (string.substring(highlightEnd, string.length()).contains("[[")) { highlightStart = string.indexOf("[["); highlightEnd = string.indexOf("]]") - 2; spanPositions.add(new Pair<>(highlightStart, highlightEnd)); string = string.replaceFirst("\\[\\[", "").replaceFirst("]]", ""); if (highlightColor != null && colorHighlightStart <= highlightStart && colorHighlightEnd >= highlightEnd) { // need to deduct the [[ and ]] from the color span colorHighlightEnd -= 4; } } SpannableString highlightSpannable = new SpannableString(string); for (Pair<Integer, Integer> spanPosition : spanPositions) { highlightSpannable.setSpan(new CustomTypefaceSpan("", context.getResources().getString(typefaceRes)), spanPosition.first, spanPosition.second, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if (highlightColor != null) { highlightSpannable.setSpan(new ForegroundColorSpan(highlightColor), colorHighlightStart, colorHighlightEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } return highlightSpannable; } public static void linkifyText(TextView textView, final int highlightColor, boolean bold, final Runnable onClick) { linkifyText(textView, highlightColor, bold, true, onClick); } /** * Will highlight a single section of a TextView between 2 "_" marks, and make it clickable. * Tapping the highlighted region will run the specified Runnable. * For example, in "Click _HERE_ please." "HERE" will be made the color specified and bolded. * * CAUTION: This does not work together with textAllCaps attribute! * @param textView * @param highlightColor * @param bold * @param underline * @param onClick */ public static void linkifyText(TextView textView, final int highlightColor, boolean bold, final boolean underline, final Runnable onClick) { linkifyText(textView, highlightColor, bold ? R.string.wire__typeface__medium : -1, true, onClick); } public static void linkifyText(TextView textView, final int highlightColor, int boldTypeface, final boolean underline, final Runnable onClick) { textView.setMovementMethod(LinkMovementMethod.getInstance()); final String string = textView.getText().toString(); final int highlightStart = string.indexOf('_'); if (highlightStart < 0) { Timber.e("Failed to highlight text - could not find _ marker in string."); return; } final int highlightEnd = string.lastIndexOf('_') - 1; if (highlightStart >= highlightEnd) { Timber.e("Failed to highlight text - make sure you have 2 _ markers to denote start and end of highlight region"); return; } final SpannableStringBuilder str = new SpannableStringBuilder(textView.getText()); str.replace(highlightStart, (highlightStart + 1), ""); str.replace(highlightEnd, (highlightEnd + 1), ""); final Typeface typeface = textView.getTypeface(); ClickableSpan linkSpan = new ClickableSpan() { @Override public void onClick(View widget) { if (onClick == null) { return; } onClick.run(); } @Override public void updateDrawState(TextPaint ds) { ds.setUnderlineText(underline); ds.setTypeface(typeface); ds.setColor(highlightColor); } }; str.setSpan(linkSpan, highlightStart, highlightEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if (boldTypeface > 0) { str.setSpan(new CustomTypefaceSpan("", textView.getResources().getString(boldTypeface)), highlightStart, highlightEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } textView.setText(str); } public static class CustomTypefaceSpan extends TypefaceSpan { public static final Parcelable.Creator<CustomTypefaceSpan> CREATOR = new Parcelable.Creator<CustomTypefaceSpan>() { public CustomTypefaceSpan createFromParcel(Parcel in) { return new CustomTypefaceSpan(in); } public CustomTypefaceSpan[] newArray(int size) { return new CustomTypefaceSpan[size]; } }; private final String type; private final Typeface newType; public CustomTypefaceSpan(String family, String type) { super(family); this.type = type; this.newType = TypefaceUtils.getTypeface(type); } public CustomTypefaceSpan(Parcel in) { super(in); this.type = in.readString(); this.newType = TypefaceUtils.getTypeface(type); } @Override public void updateDrawState(@NonNull TextPaint ds) { applyCustomTypeFace(ds, newType); } @Override public void updateMeasureState(@NonNull TextPaint paint) { applyCustomTypeFace(paint, newType); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeString(type); } private static void applyCustomTypeFace(Paint paint, Typeface tf) { paint.setTypeface(tf); } } }