Implemented simple Kodi markup code applier for the Now Playing fragment (#471)

* Implemented simple Kodi markup code applier for the Now Playing fragment
* Moved to UIUtils; added early exit; now handles nesting of the same type
* Added handlers for LOWERCASE, CAPITALIZE, LIGHT and COLOR
- LOWERCASE and CAPITALIZE work, LIGHT and COLOR are just stripped out
- inlined the TextAppearanceSpan new's. Turns out they can't be reused.
* updated javadoc
* fixed crash when capitalizing an empty word
This commit is contained in:
Mon Zafra 2017-10-19 16:05:45 +08:00 committed by Synced Synapse
parent 41a433c985
commit 45ecfc2e25
3 changed files with 189 additions and 1 deletions

View File

@ -21,6 +21,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.MenuItem;
@ -776,7 +777,7 @@ public class NowPlayingFragment extends Fragment
if (!TextUtils.isEmpty(descriptionPlot)) {
mediaDescription.setVisibility(View.VISIBLE);
mediaDescription.setText(descriptionPlot);
mediaDescription.setText(UIUtils.applyMarkup(getContext(), descriptionPlot));
} else {
mediaDescription.setVisibility(View.GONE);
}

View File

@ -30,7 +30,9 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AlertDialog;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
@ -54,6 +56,8 @@ import org.xbmc.kore.ui.widgets.RepeatModeButton;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* General UI Utils
@ -607,4 +611,181 @@ public class UIUtils {
button.setMode(RepeatModeButton.MODE.ALL);
}
}
/**
* Replaces some BBCode-ish tagged text with styled spans.
* <p>
* Recognizes and styles CR, B, I, UPPERCASE, LOWERCASE and CAPITALIZE; recognizes
* and strips out LIGHT and COLOR. This is very strict/dumb, it only recognizes
* uppercase tags with no spaces around them.
*
* @param context Activity context needed to resolve the style resources
* @param src The text to style
* @return a styled CharSequence that can be passed to a {@link TextView#setText(CharSequence)}
* or derivatives.
*/
public static SpannableStringBuilder applyMarkup(Context context, String src) {
SpannableStringBuilder sb = new SpannableStringBuilder();
int start = src.indexOf('[');
if (start == -1) {
sb.append(src);
return sb;
}
if (start > 0) {
sb.append(src, 0, start);
}
Nestable upper = new Nestable();
Nestable lower = new Nestable();
Nestable title = new Nestable();
Nestable bold = new Nestable();
Nestable italic = new Nestable();
Nestable light = new Nestable();
Nestable color = new Nestable();
Pattern colorTag = Pattern.compile("^\\[COLOR [^\\]]+\\]");
for (int i = start, length = src.length(); i < length;) {
String s = src.substring(i);
int nextTag = s.indexOf('[');
if (nextTag == -1) {
sb.append(s);
break;
}
if (nextTag > 0) {
sb.append(s, 0, nextTag);
i += nextTag;
} else if (s.startsWith("[CR]")) {
sb.append('\n');
i += 4;
} else if (s.startsWith("[UPPERCASE]")) {
if (upper.start()) {
upper.index = sb.length();
}
i += 11;
} else if (s.startsWith("[/UPPERCASE]")) {
if (upper.end()) {
String sub = sb.subSequence(upper.index, sb.length()).toString();
sb.replace(upper.index, sb.length(), sub.toUpperCase());
} else if (upper.imbalanced()) {
sb.append("[/UPPERCASE]");
}
i += 12;
} else if (s.startsWith("[B]")) {
if (bold.start()) {
bold.index = sb.length();
}
i += 3;
} else if (s.startsWith("[/B]")) {
if (bold.end()) {
sb.setSpan(new TextAppearanceSpan(context, R.style.TextAppearance_Bold),
bold.index, sb.length(), 0);
} else if (bold.imbalanced()) {
sb.append("[/B]");
}
i += 4;
} else if (s.startsWith("[I]")) {
if (italic.start()) {
italic.index = sb.length();
}
i += 3;
} else if (s.startsWith("[/I]")) {
if (italic.end()) {
sb.setSpan(new TextAppearanceSpan(context, R.style.TextAppearance_Italic),
italic.index, sb.length(), 0);
} else if (italic.imbalanced()) {
sb.append("[/I]");
}
i += 4;
} else if (s.startsWith("[LOWERCASE]")) {
if (lower.start()) {
lower.index = sb.length();
}
i += 11;
} else if (s.startsWith("[/LOWERCASE]")) {
if (lower.end()) {
String sub = sb.subSequence(lower.index, sb.length()).toString();
sb.replace(lower.index, sb.length(), sub.toLowerCase());
} else if (lower.imbalanced()) {
sb.append("[/LOWERCASE]");
}
i += 12;
} else if (s.startsWith("[CAPITALIZE]")) {
if (title.start()) {
title.index = sb.length();
}
i += 12;
} else if (s.startsWith("[/CAPITALIZE]")) {
if (title.end()) {
String sub = sb.subSequence(title.index, sb.length()).toString();
sb.replace(title.index, sb.length(), toTitleCase(sub));
} else if (title.imbalanced()) {
sb.append("[/CAPITALIZE]");
}
i += 13;
} else if (s.startsWith("[LIGHT]")) {
light.start();
i += 7;
} else if (s.startsWith("[/LIGHT]")) {
light.end();
if (light.imbalanced()) {
sb.append("[/LIGHT]");
}
i += 8;
} else if (s.startsWith("[/COLOR]")) {
color.end();
if (color.imbalanced()) {
sb.append("[/COLOR]");
}
i += 8;
} else {
Matcher m = colorTag.matcher(s);
if (m.find()) {
color.start();
i += m.end();
} else {
sb.append('[');
i += 1;
}
}
}
return sb;
}
private static class Nestable {
int index = 0;
int level = 0;
/**
* @return true if we just opened the first tag
*/
boolean start() {
return level++ == 0;
}
/**
* @return true if we just closed the last open tag
*/
boolean end() {
return --level == 0;
}
/**
* @return true if we found a close tag when there are no open tags
*/
boolean imbalanced() {
if (level < 0) {
level = 0;
return true;
}
return false;
}
}
private static String toTitleCase(String text) {
StringBuilder sb = new StringBuilder();
for (String word : text.toLowerCase().split("\\b")) {
if (word.isEmpty()) continue;
sb.append(Character.toUpperCase(word.charAt(0)));
sb.append(word, 1, word.length());
}
return sb.toString();
}
}

View File

@ -325,5 +325,11 @@
<item name="android:textSize">@dimen/text_size_xlarge</item>
</style>
<style name="TextAppearance.Bold">
<item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance.Italic">
<item name="android:textStyle">italic</item>
</style>
</resources>