That fuck shit the fascists are using
1package org.tm.archive.util;
2
3import android.content.Context;
4import android.content.res.Resources;
5import android.graphics.Color;
6import android.graphics.Typeface;
7import android.graphics.drawable.ColorDrawable;
8import android.graphics.drawable.Drawable;
9import android.os.Build;
10import android.text.Layout;
11import android.text.Spannable;
12import android.text.SpannableString;
13import android.text.SpannableStringBuilder;
14import android.text.Spanned;
15import android.text.TextPaint;
16import android.text.TextUtils;
17import android.text.style.AbsoluteSizeSpan;
18import android.text.style.AlignmentSpan;
19import android.text.style.BulletSpan;
20import android.text.style.CharacterStyle;
21import android.text.style.ClickableSpan;
22import android.text.style.DynamicDrawableSpan;
23import android.text.style.ForegroundColorSpan;
24import android.text.style.ImageSpan;
25import android.text.style.RelativeSizeSpan;
26import android.text.style.StyleSpan;
27import android.text.style.TextAppearanceSpan;
28import android.text.style.TypefaceSpan;
29import android.text.style.URLSpan;
30import android.view.View;
31
32import androidx.annotation.ColorInt;
33import androidx.annotation.NonNull;
34import androidx.annotation.StringRes;
35import androidx.annotation.StyleRes;
36import androidx.core.content.ContextCompat;
37
38import org.signal.core.util.DimensionUnit;
39import org.tm.archive.R;
40
41public final class SpanUtil {
42
43 private SpanUtil() {}
44
45 public static final String SPAN_PLACE_HOLDER = "<<<SPAN>>>";
46
47 private final static Typeface MEDIUM_BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.BOLD);
48 private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
49 private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
50
51 public static CharSequence center(@NonNull CharSequence sequence) {
52 SpannableString spannable = new SpannableString(sequence);
53 spannable.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
54 return spannable;
55 }
56
57 public static CharSequence textAppearance(@NonNull Context context, @StyleRes int textAppearance, @NonNull CharSequence sequence) {
58 SpannableString spannable = new SpannableString(sequence);
59 spannable.setSpan(new TextAppearanceSpan(context, textAppearance), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
60 return spannable;
61 }
62
63 public static CharSequence italic(CharSequence sequence) {
64 return italic(sequence, sequence.length());
65 }
66
67 public static CharSequence italic(CharSequence sequence, int length) {
68 SpannableString spannable = new SpannableString(sequence);
69 spannable.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
70 return spannable;
71 }
72
73 public static CharSequence small(CharSequence sequence) {
74 SpannableString spannable = new SpannableString(sequence);
75 spannable.setSpan(new RelativeSizeSpan(0.9f), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
76 return spannable;
77 }
78
79 public static CharSequence ofSize(CharSequence sequence, int size) {
80 SpannableString spannable = new SpannableString(sequence);
81 spannable.setSpan(new AbsoluteSizeSpan(size, true), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
82 return spannable;
83 }
84
85 public static CharSequence bold(CharSequence sequence) {
86 SpannableString spannable = new SpannableString(sequence);
87 spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
88 return spannable;
89 }
90
91 public static CharSequence boldSubstring(CharSequence fullString, CharSequence substring) {
92 SpannableString spannable = new SpannableString(fullString);
93 int start = TextUtils.indexOf(fullString, substring);
94 int end = start + substring.length();
95
96 if (start >= 0 && end <= fullString.length()) {
97 spannable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
98 }
99 return spannable;
100 }
101
102 public static CharSequence color(int color, CharSequence sequence) {
103 SpannableString spannable = new SpannableString(sequence);
104 spannable.setSpan(new ForegroundColorSpan(color), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
105 return spannable;
106 }
107
108 public static @NonNull CharSequence bullet(@NonNull CharSequence sequence, int gapWidth) {
109 SpannableString spannable = new SpannableString(sequence);
110 spannable.setSpan(new BulletSpan(gapWidth), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
111 return spannable;
112 }
113
114 public static CharSequence buildImageSpan(@NonNull Drawable drawable) {
115 int flag = Build.VERSION.SDK_INT >= 29 ? DynamicDrawableSpan.ALIGN_CENTER : DynamicDrawableSpan.ALIGN_BASELINE;
116
117 return buildImageSpan(drawable, flag);
118 }
119
120 private static CharSequence buildImageSpan(@NonNull Drawable drawable, int flag) {
121 SpannableString imageSpan = new SpannableString(" ");
122
123 imageSpan.setSpan(new ImageSpan(drawable, flag), 0, imageSpan.length(), 0);
124
125 return imageSpan;
126 }
127
128 public static CharSequence buildCenteredImageSpan(@NonNull Drawable drawable) {
129 SpannableString imageSpan = new SpannableString(" ");
130 imageSpan.setSpan(new CenteredImageSpan(drawable), 0, imageSpan.length(), 0);
131 return imageSpan;
132 }
133
134 public static CharSequence space(int width, @NonNull DimensionUnit widthUnit) {
135 Drawable drawable = new ColorDrawable(Color.TRANSPARENT);
136 drawable.setBounds(0, 0, (int) widthUnit.toPixels(width), 1);
137
138 return buildCenteredImageSpan(drawable);
139 }
140
141 public static void appendCenteredImageSpan(@NonNull SpannableStringBuilder builder, @NonNull Drawable drawable, int width, int height) {
142 drawable.setBounds(0, 0, ViewUtil.dpToPx(width), ViewUtil.dpToPx(height));
143 builder.append(" ").append(SpanUtil.buildCenteredImageSpan(drawable));
144 }
145
146 public static void appendSpacer(@NonNull SpannableStringBuilder builder, int width) {
147 SpanUtil.appendCenteredImageSpanWithoutSpace(builder, new ColorDrawable(Color.TRANSPARENT), width, 8);
148 }
149
150 public static void appendCenteredImageSpanWithoutSpace(@NonNull SpannableStringBuilder builder, @NonNull Drawable drawable, int width, int height) {
151 drawable.setBounds(0, 0, ViewUtil.dpToPx(width), ViewUtil.dpToPx(height));
152 builder.append(SpanUtil.buildCenteredImageSpan(drawable));
153 }
154
155 public static CharSequence learnMore(@NonNull Context context,
156 @ColorInt int color,
157 @NonNull View.OnClickListener onLearnMoreClicked)
158 {
159 String learnMore = context.getString(R.string.LearnMoreTextView_learn_more);
160 return clickSubstring(learnMore, learnMore, onLearnMoreClicked, color);
161 }
162
163 public static CharSequence readMore(@NonNull Context context,
164 @ColorInt int color,
165 @NonNull View.OnClickListener onLearnMoreClicked)
166 {
167 String readMore = context.getString(R.string.SpanUtil__read_more);
168 return clickSubstring(readMore, readMore, onLearnMoreClicked, color);
169 }
170
171 public static CharSequence clickable(@NonNull CharSequence text,
172 @ColorInt int color,
173 @NonNull View.OnClickListener onLearnMoreClicked)
174 {
175 return clickSubstring(text, text, onLearnMoreClicked, color);
176 }
177
178 public static Spanned urlSubsequence(@NonNull CharSequence fullString, @NonNull CharSequence substring, @NonNull String url) {
179 SpannableString spannable = new SpannableString(fullString);
180 int start = TextUtils.indexOf(fullString, substring);
181 int end = start + substring.length();
182
183 if (start >= 0 && end <= fullString.length()) {
184 spannable.setSpan(new URLSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
185 }
186 return spannable;
187 }
188
189 /**
190 * Takes two resources:
191 * - one resource that has a single string placeholder
192 * - and another resource for a string you want to put in that placeholder with a click listener.
193 *
194 * Example:
195 *
196 * <string name="main_string">This is a %1$s string.</string>
197 * <string name="clickable_string">clickable</string>
198 *
199 * -> This is a clickable string.
200 * (where "clickable" is blue and will trigger the provided click listener when clicked)
201 */
202 public static Spannable clickSubstring(@NonNull Context context, @StringRes int mainString, @StringRes int clickableString, @NonNull View.OnClickListener clickListener) {
203 String main = context.getString(mainString, SPAN_PLACE_HOLDER);
204 String clickable = context.getString(clickableString);
205
206 int start = main.indexOf(SPAN_PLACE_HOLDER);
207 int end = start + SPAN_PLACE_HOLDER.length();
208
209 Spannable spannable = new SpannableString(main.substring(0, start) + clickable + main.substring(end));
210
211 spannable.setSpan(new ClickableSpan() {
212 @Override
213 public void onClick(@NonNull View widget) {
214 clickListener.onClick(widget);
215 }
216
217 @Override
218 public void updateDrawState(@NonNull TextPaint ds) {
219 super.updateDrawState(ds);
220 ds.setUnderlineText(false);
221 }
222 }, start, start + clickable.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
223
224 return spannable;
225 }
226
227 public static CharSequence clickSubstring(@NonNull Context context,
228 @NonNull CharSequence fullString,
229 @NonNull CharSequence substring,
230 @NonNull View.OnClickListener clickListener) {
231 return clickSubstring(fullString,
232 substring,
233 clickListener,
234 ContextCompat.getColor(context, R.color.signal_accent_primary));
235 }
236
237 public static CharSequence clickSubstring(@NonNull CharSequence fullString,
238 @NonNull CharSequence substring,
239 @NonNull View.OnClickListener clickListener,
240 @ColorInt int linkColor)
241 {
242 ClickableSpan clickable = new ClickableSpan() {
243 @Override
244 public void updateDrawState(@NonNull TextPaint ds) {
245 super.updateDrawState(ds);
246 ds.setUnderlineText(false);
247 ds.setColor(linkColor);
248 }
249
250 @Override
251 public void onClick(@NonNull View widget) {
252 clickListener.onClick(widget);
253 }
254 };
255
256 SpannableString spannable = new SpannableString(fullString);
257 int start = TextUtils.indexOf(fullString, substring);
258 int end = start + substring.length();
259
260 if (start >= 0 && end <= fullString.length()) {
261 spannable.setSpan(clickable, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
262 }
263
264 return spannable;
265 }
266
267 public static @NonNull CharSequence insertSingleSpan(@NonNull Resources resources, @StringRes int res, @NonNull CharSequence span) {
268 return replacePlaceHolder(resources.getString(res, SPAN_PLACE_HOLDER), span);
269 }
270
271 public static CharSequence replacePlaceHolder(@NonNull String string, @NonNull CharSequence span) {
272 int index = string.indexOf(SpanUtil.SPAN_PLACE_HOLDER);
273 if (index == -1) {
274 return string;
275 }
276 SpannableStringBuilder builder = new SpannableStringBuilder(string);
277 builder.replace(index, index + SpanUtil.SPAN_PLACE_HOLDER.length(), span);
278 return builder;
279 }
280
281 public static CharacterStyle getMediumBoldSpan() {
282 if (Build.VERSION.SDK_INT >= 28) {
283 return new TypefaceSpan(MEDIUM_BOLD_TYPEFACE);
284 } else {
285 return new StyleSpan(Typeface.BOLD);
286 }
287 }
288
289 public static CharacterStyle getBoldSpan() {
290 if (Build.VERSION.SDK_INT >= 28) {
291 return new TypefaceSpan(BOLD_TYPEFACE);
292 } else {
293 return new StyleSpan(Typeface.BOLD);
294 }
295 }
296
297 public static CharacterStyle getNormalSpan() {
298 if (Build.VERSION.SDK_INT >= 28) {
299 return new TypefaceSpan(LIGHT_TYPEFACE);
300 } else {
301 return new StyleSpan(Typeface.NORMAL);
302 }
303 }
304}