Add battery bar colour expressions
This commit is contained in:
+83
-10
@@ -7,40 +7,66 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import se.ajpanton.statusbartweak.shell.settings.BatteryBarStyle;
|
import se.ajpanton.statusbartweak.shell.settings.BatteryBarStyle;
|
||||||
|
|
||||||
final class ClockColorResolver {
|
public final class ClockColorResolver {
|
||||||
private static final int CHANNEL_ALPHA = 0;
|
private static final int CHANNEL_ALPHA = 0;
|
||||||
private static final int CHANNEL_RED = 1;
|
private static final int CHANNEL_RED = 1;
|
||||||
private static final int CHANNEL_GREEN = 2;
|
private static final int CHANNEL_GREEN = 2;
|
||||||
private static final int CHANNEL_BLUE = 3;
|
private static final int CHANNEL_BLUE = 3;
|
||||||
private static final int CHANNEL_COUNT = 4;
|
private static final int CHANNEL_COUNT = 4;
|
||||||
|
|
||||||
String resolveColorHex(
|
public String resolveColorHex(
|
||||||
Context context,
|
Context context,
|
||||||
String expression,
|
String expression,
|
||||||
int referenceColor,
|
int referenceColor,
|
||||||
int fallbackColor
|
int fallbackColor
|
||||||
) {
|
) {
|
||||||
int resolved = resolveColor(context, expression, referenceColor, fallbackColor);
|
int resolved = resolveColor(context, expression, referenceColor, fallbackColor);
|
||||||
|
return colorHex(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveColorHex(
|
||||||
|
Context context,
|
||||||
|
String expression,
|
||||||
|
int referenceColor,
|
||||||
|
int fallbackColor,
|
||||||
|
Options options
|
||||||
|
) {
|
||||||
|
int resolved = resolveColor(context, expression, referenceColor, fallbackColor, options);
|
||||||
|
return colorHex(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String colorHex(int resolved) {
|
||||||
if (alpha(resolved) >= 255) {
|
if (alpha(resolved) >= 255) {
|
||||||
return String.format(Locale.ROOT, "#%06X", resolved & 0xFFFFFF);
|
return String.format(Locale.ROOT, "#%06X", resolved & 0xFFFFFF);
|
||||||
}
|
}
|
||||||
return String.format(Locale.ROOT, "#%08X", resolved);
|
return String.format(Locale.ROOT, "#%08X", resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
int resolveColor(
|
public int resolveColor(
|
||||||
Context context,
|
Context context,
|
||||||
String expression,
|
String expression,
|
||||||
int referenceColor,
|
int referenceColor,
|
||||||
int fallbackColor
|
int fallbackColor
|
||||||
|
) {
|
||||||
|
return resolveColor(context, expression, referenceColor, fallbackColor, Options.clock());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int resolveColor(
|
||||||
|
Context context,
|
||||||
|
String expression,
|
||||||
|
int referenceColor,
|
||||||
|
int fallbackColor,
|
||||||
|
Options options
|
||||||
) {
|
) {
|
||||||
if (isEmpty(expression)) {
|
if (isEmpty(expression)) {
|
||||||
return fallbackColor;
|
return fallbackColor;
|
||||||
}
|
}
|
||||||
|
Options resolvedOptions = options != null ? options : Options.clock();
|
||||||
VariantExpressions variants = splitVariants(expression);
|
VariantExpressions variants = splitVariants(expression);
|
||||||
Value bright = evaluateExpression(context, variants.brightExpression, null, fallbackColor);
|
Value bright = evaluateExpression(context, variants.brightExpression, null, fallbackColor, resolvedOptions);
|
||||||
Value dark = variants.darkExpression.isEmpty()
|
Value dark = variants.darkExpression.isEmpty()
|
||||||
? bright
|
? bright
|
||||||
: evaluateExpression(context, variants.darkExpression, bright, fallbackColor);
|
: evaluateExpression(context, variants.darkExpression, bright, fallbackColor, resolvedOptions);
|
||||||
return toColor(isDark(referenceColor) ? dark : bright);
|
return toColor(isDark(referenceColor) ? dark : bright);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +74,8 @@ final class ClockColorResolver {
|
|||||||
Context context,
|
Context context,
|
||||||
String expression,
|
String expression,
|
||||||
Value inherited,
|
Value inherited,
|
||||||
int fallbackColor
|
int fallbackColor,
|
||||||
|
Options options
|
||||||
) {
|
) {
|
||||||
ArrayList<String> tokens = tokenize(expression);
|
ArrayList<String> tokens = tokenize(expression);
|
||||||
ArrayList<Value> stack = new ArrayList<>();
|
ArrayList<Value> stack = new ArrayList<>();
|
||||||
@@ -56,7 +83,7 @@ final class ClockColorResolver {
|
|||||||
stack.add(inherited.copy());
|
stack.add(inherited.copy());
|
||||||
}
|
}
|
||||||
for (String token : tokens) {
|
for (String token : tokens) {
|
||||||
applyToken(context, stack, token, fallbackColor);
|
applyToken(context, stack, token, fallbackColor, options);
|
||||||
}
|
}
|
||||||
if (stack.isEmpty()) {
|
if (stack.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Empty colour expression");
|
throw new IllegalArgumentException("Empty colour expression");
|
||||||
@@ -64,7 +91,13 @@ final class ClockColorResolver {
|
|||||||
return top(stack).copy();
|
return top(stack).copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyToken(Context context, ArrayList<Value> stack, String token, int fallbackColor) {
|
private void applyToken(
|
||||||
|
Context context,
|
||||||
|
ArrayList<Value> stack,
|
||||||
|
String token,
|
||||||
|
int fallbackColor,
|
||||||
|
Options options
|
||||||
|
) {
|
||||||
if (isEmpty(token)) {
|
if (isEmpty(token)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -73,7 +106,7 @@ final class ClockColorResolver {
|
|||||||
applyOperation(stack, operation);
|
applyOperation(stack, operation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stack.add(parseOperand(context, token, fallbackColor));
|
stack.add(parseOperand(context, token, fallbackColor, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Operation parseOperation(String token) {
|
private Operation parseOperation(String token) {
|
||||||
@@ -244,12 +277,15 @@ final class ClockColorResolver {
|
|||||||
stack.add(Value.vector(result));
|
stack.add(Value.vector(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Value parseOperand(Context context, String token, int fallbackColor) {
|
private Value parseOperand(Context context, String token, int fallbackColor, Options options) {
|
||||||
String value = token != null ? token.trim() : "";
|
String value = token != null ? token.trim() : "";
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Empty colour token");
|
throw new IllegalArgumentException("Empty colour token");
|
||||||
}
|
}
|
||||||
if ("#batterybar".equalsIgnoreCase(value)) {
|
if ("#batterybar".equalsIgnoreCase(value)) {
|
||||||
|
if (options == null || !options.allowBatteryBarOperand) {
|
||||||
|
throw new IllegalArgumentException("#batterybar is not supported here");
|
||||||
|
}
|
||||||
return Value.vector(vectorForColor(BatteryBarStyle.resolveCurrentColor(context, fallbackColor, fallbackColor)));
|
return Value.vector(vectorForColor(BatteryBarStyle.resolveCurrentColor(context, fallbackColor, fallbackColor)));
|
||||||
}
|
}
|
||||||
if (value.charAt(0) == '#') {
|
if (value.charAt(0) == '#') {
|
||||||
@@ -258,6 +294,9 @@ final class ClockColorResolver {
|
|||||||
if (value.charAt(0) == '[') {
|
if (value.charAt(0) == '[') {
|
||||||
return Value.vector(parseVector(value));
|
return Value.vector(parseVector(value));
|
||||||
}
|
}
|
||||||
|
if (options != null && options.allowBareHexOperand && looksLikeBareHex(value)) {
|
||||||
|
return Value.vector(vectorForColor(parseHexColor("#" + value)));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return Value.number(Double.parseDouble(value));
|
return Value.number(Double.parseDouble(value));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@@ -265,6 +304,22 @@ final class ClockColorResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean looksLikeBareHex(String value) {
|
||||||
|
int length = value != null ? value.length() : 0;
|
||||||
|
if (length != 3 && length != 4 && length != 6 && length != 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean hasHexLetter = false;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
char c = value.charAt(i);
|
||||||
|
if (!isHexDigit(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hasHexLetter |= (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
return length != 3 || hasHexLetter;
|
||||||
|
}
|
||||||
|
|
||||||
private double[] parseVector(String token) {
|
private double[] parseVector(String token) {
|
||||||
String value = token != null ? token.trim() : "";
|
String value = token != null ? token.trim() : "";
|
||||||
if (!value.startsWith("[") || !value.endsWith("]")) {
|
if (!value.startsWith("[") || !value.endsWith("]")) {
|
||||||
@@ -608,4 +663,22 @@ final class ClockColorResolver {
|
|||||||
return Math.max(0d, Math.min(255d, value));
|
return Math.max(0d, Math.min(255d, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Options {
|
||||||
|
private final boolean allowBatteryBarOperand;
|
||||||
|
private final boolean allowBareHexOperand;
|
||||||
|
|
||||||
|
private Options(boolean allowBatteryBarOperand, boolean allowBareHexOperand) {
|
||||||
|
this.allowBatteryBarOperand = allowBatteryBarOperand;
|
||||||
|
this.allowBareHexOperand = allowBareHexOperand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options clock() {
|
||||||
|
return new Options(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Options batteryBar() {
|
||||||
|
return new Options(false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-346
@@ -1,394 +1,105 @@
|
|||||||
package se.ajpanton.statusbartweak.shell.settings;
|
package se.ajpanton.statusbartweak.shell.settings;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import se.ajpanton.statusbartweak.runtime.features.clock.ClockColorResolver;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public final class ColourExpressionSupport {
|
public final class ColourExpressionSupport {
|
||||||
private static final Pattern OPERATION_PATTERN =
|
private static final ClockColorResolver RESOLVER = new ClockColorResolver();
|
||||||
Pattern.compile("([+\\-*/])\\s*(" + hexOperandPattern() + "|\\d+(?:\\.\\d+)?|\\.\\d+)");
|
private static final ClockColorResolver.Options OPTIONS = ClockColorResolver.Options.batteryBar();
|
||||||
private static final Pattern LEADING_HEX_PATTERN =
|
|
||||||
Pattern.compile("(?i)^#?(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})");
|
|
||||||
|
|
||||||
private ColourExpressionSupport() {
|
private ColourExpressionSupport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String normaliseExpression(String raw, boolean assumeHashPrefix) {
|
public static String normaliseExpression(String raw, boolean assumeHashPrefix) {
|
||||||
if (raw == null) {
|
String value = normaliseSeparators(raw);
|
||||||
return "";
|
|
||||||
}
|
|
||||||
String value = raw.trim();
|
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
int variantSplit = findVariantSplit(value);
|
try {
|
||||||
String left = value;
|
RESOLVER.resolveColor(null, value, Color.WHITE, Color.WHITE, OPTIONS);
|
||||||
String right = null;
|
RESOLVER.resolveColor(null, value, Color.BLACK, Color.WHITE, OPTIONS);
|
||||||
if (variantSplit >= 0) {
|
return value;
|
||||||
left = value.substring(0, variantSplit).trim();
|
} catch (IllegalArgumentException ignored) {
|
||||||
right = value.substring(variantSplit + 1).trim();
|
|
||||||
}
|
|
||||||
if (!isValidSegment(left, assumeHashPrefix, false, Color.WHITE)) {
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (right != null && !isValidSegment(right, assumeHashPrefix, true, Color.WHITE)) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
return left + "|" + right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int resolveColor(String raw,
|
public static int resolveColor(
|
||||||
int referenceColor,
|
String raw,
|
||||||
int fallbackColor,
|
int referenceColor,
|
||||||
boolean assumeHashPrefix) {
|
int fallbackColor,
|
||||||
String value = normaliseExpression(raw, assumeHashPrefix);
|
boolean assumeHashPrefix
|
||||||
|
) {
|
||||||
|
String value = normaliseSeparators(raw);
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
return fallbackColor;
|
return fallbackColor;
|
||||||
}
|
}
|
||||||
int variantSplit = findVariantSplit(value);
|
|
||||||
if (variantSplit < 0) {
|
|
||||||
return resolveSegment(value, fallbackColor, assumeHashPrefix, false);
|
|
||||||
}
|
|
||||||
String left = value.substring(0, variantSplit).trim();
|
|
||||||
String right = value.substring(variantSplit + 1).trim();
|
|
||||||
int brightVariant = resolveSegment(left, fallbackColor, assumeHashPrefix, false);
|
|
||||||
if (!isDark(referenceColor)) {
|
|
||||||
return brightVariant;
|
|
||||||
}
|
|
||||||
return resolveSegment(right, brightVariant, assumeHashPrefix, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String resolveColorHex(String raw,
|
|
||||||
int referenceColor,
|
|
||||||
int fallbackColor,
|
|
||||||
boolean assumeHashPrefix) {
|
|
||||||
int resolved = resolveColor(raw, referenceColor, fallbackColor, assumeHashPrefix);
|
|
||||||
int alpha = Color.alpha(resolved);
|
|
||||||
if (alpha >= 255) {
|
|
||||||
return String.format(Locale.ROOT, "#%06X", resolved & 0xFFFFFF);
|
|
||||||
}
|
|
||||||
return String.format(Locale.ROOT, "#%08X", resolved);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isValidSegment(String segment,
|
|
||||||
boolean assumeHashPrefix,
|
|
||||||
boolean allowInheritedBase,
|
|
||||||
int baseColor) {
|
|
||||||
if (TextUtils.isEmpty(segment)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
resolveSegment(segment, baseColor, assumeHashPrefix, allowInheritedBase);
|
return RESOLVER.resolveColor(null, value, referenceColor, fallbackColor, OPTIONS);
|
||||||
return true;
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
return false;
|
return fallbackColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int resolveSegment(String segment,
|
public static String resolveColorHex(
|
||||||
int baseColor,
|
String raw,
|
||||||
boolean assumeHashPrefix,
|
int referenceColor,
|
||||||
boolean allowInheritedBase) {
|
int fallbackColor,
|
||||||
String value = segment != null ? segment.trim() : "";
|
boolean assumeHashPrefix
|
||||||
|
) {
|
||||||
|
String value = normaliseSeparators(raw);
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Empty colour segment");
|
return hexFor(fallbackColor);
|
||||||
}
|
|
||||||
int index = 0;
|
|
||||||
boolean invert = false;
|
|
||||||
if (value.regionMatches(true, 0, "inv", 0, 3)) {
|
|
||||||
invert = true;
|
|
||||||
index = 3;
|
|
||||||
}
|
|
||||||
double[] channels;
|
|
||||||
double alpha;
|
|
||||||
String remainder;
|
|
||||||
if (startsWithOperator(value, index)) {
|
|
||||||
if (!allowInheritedBase) {
|
|
||||||
throw new IllegalArgumentException("Missing starting colour");
|
|
||||||
}
|
|
||||||
channels = channelsFor(baseColor);
|
|
||||||
alpha = Color.alpha(baseColor);
|
|
||||||
remainder = value.substring(index).trim();
|
|
||||||
} else {
|
|
||||||
LeadingColour leading = parseLeadingColour(value, index, assumeHashPrefix);
|
|
||||||
channels = channelsFor(leading.color);
|
|
||||||
alpha = Color.alpha(leading.color);
|
|
||||||
remainder = value.substring(leading.endIndex).trim();
|
|
||||||
}
|
|
||||||
if (invert) {
|
|
||||||
invertChannels(channels);
|
|
||||||
}
|
|
||||||
alpha = applyOperations(channels, alpha, remainder);
|
|
||||||
return colorFromChannels(channels, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LeadingColour parseLeadingColour(String value,
|
|
||||||
int start,
|
|
||||||
boolean assumeHashPrefix) {
|
|
||||||
String remainder = value.substring(start).trim();
|
|
||||||
Matcher matcher = LEADING_HEX_PATTERN.matcher(remainder);
|
|
||||||
if (!matcher.find()) {
|
|
||||||
throw new IllegalArgumentException("Missing starting colour");
|
|
||||||
}
|
|
||||||
String token = matcher.group();
|
|
||||||
if (assumeHashPrefix && token.charAt(0) != '#') {
|
|
||||||
token = "#" + token;
|
|
||||||
}
|
|
||||||
int color = parseHexColor(token, assumeHashPrefix);
|
|
||||||
return new LeadingColour(color, start + matcher.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double applyOperations(double[] channels, double alpha, String remainder) {
|
|
||||||
int index = 0;
|
|
||||||
while (index < remainder.length()) {
|
|
||||||
while (index < remainder.length() && Character.isWhitespace(remainder.charAt(index))) {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
if (index >= remainder.length()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Matcher matcher = OPERATION_PATTERN.matcher(remainder);
|
|
||||||
matcher.region(index, remainder.length());
|
|
||||||
if (!matcher.lookingAt()) {
|
|
||||||
throw new IllegalArgumentException("Invalid colour operation");
|
|
||||||
}
|
|
||||||
char operator = matcher.group(1).charAt(0);
|
|
||||||
String operandText = matcher.group(2);
|
|
||||||
alpha = applyOperation(channels, alpha, operator, operandText);
|
|
||||||
index = matcher.end();
|
|
||||||
}
|
|
||||||
return alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double applyOperation(double[] channels, double alpha, char operator, String operandText) {
|
|
||||||
Operand operand = parseOperand(operandText);
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
double value = channels[i];
|
|
||||||
double rhs = operand.rgb[i];
|
|
||||||
if (operand.isHex && (operator == '*' || operator == '/')) {
|
|
||||||
rhs = rhs / 255d;
|
|
||||||
}
|
|
||||||
switch (operator) {
|
|
||||||
case '+':
|
|
||||||
channels[i] = value + rhs;
|
|
||||||
break;
|
|
||||||
case '-':
|
|
||||||
channels[i] = value - rhs;
|
|
||||||
break;
|
|
||||||
case '*':
|
|
||||||
channels[i] = value * rhs;
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
channels[i] = rhs == 0d ? value : value / rhs;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (operand.hasAlpha) {
|
|
||||||
double rhs = operand.alpha;
|
|
||||||
if (operand.isHex && (operator == '*' || operator == '/')) {
|
|
||||||
rhs = rhs / 255d;
|
|
||||||
}
|
|
||||||
switch (operator) {
|
|
||||||
case '+':
|
|
||||||
alpha = alpha + rhs;
|
|
||||||
break;
|
|
||||||
case '-':
|
|
||||||
alpha = alpha - rhs;
|
|
||||||
break;
|
|
||||||
case '*':
|
|
||||||
alpha = alpha * rhs;
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
alpha = rhs == 0d ? alpha : alpha / rhs;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown operator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Operand parseOperand(String operandText) {
|
|
||||||
String value = operandText != null ? operandText.trim() : "";
|
|
||||||
if (value.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Missing operand");
|
|
||||||
}
|
|
||||||
if (value.charAt(0) == '#') {
|
|
||||||
return parseHexOperand(value);
|
|
||||||
}
|
|
||||||
double scalar;
|
|
||||||
try {
|
|
||||||
scalar = Double.parseDouble(value);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new IllegalArgumentException("Invalid operand", e);
|
|
||||||
}
|
|
||||||
return new Operand(new double[] { scalar, scalar, scalar }, 0d, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int parseHexColor(String raw, boolean assumeHashPrefix) {
|
|
||||||
String value = raw != null ? raw.trim() : "";
|
|
||||||
if (value.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Empty colour");
|
|
||||||
}
|
|
||||||
if (assumeHashPrefix && value.charAt(0) != '#') {
|
|
||||||
value = "#" + value;
|
|
||||||
}
|
|
||||||
if (value.charAt(0) != '#') {
|
|
||||||
throw new IllegalArgumentException("Expected colour literal");
|
|
||||||
}
|
|
||||||
String hex = value.substring(1);
|
|
||||||
if (hex.length() == 3) {
|
|
||||||
hex = expandShortHex(hex);
|
|
||||||
} else if (hex.length() == 4) {
|
|
||||||
hex = expandShortHex(hex);
|
|
||||||
} else if (hex.length() != 6 && hex.length() != 8) {
|
|
||||||
throw new IllegalArgumentException("Unsupported colour length");
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Color.parseColor("#" + hex);
|
return RESOLVER.resolveColorHex(null, value, referenceColor, fallbackColor, OPTIONS);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
throw new IllegalArgumentException("Invalid colour", e);
|
return hexFor(fallbackColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String expandShortHex(String hex) {
|
private static String normaliseSeparators(String raw) {
|
||||||
StringBuilder out = new StringBuilder(hex.length() * 2);
|
|
||||||
for (int i = 0; i < hex.length(); i++) {
|
|
||||||
char c = hex.charAt(i);
|
|
||||||
out.append(c).append(c);
|
|
||||||
}
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double[] channelsFor(int color) {
|
|
||||||
return new double[] {
|
|
||||||
Color.red(color),
|
|
||||||
Color.green(color),
|
|
||||||
Color.blue(color)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void invertChannels(double[] channels) {
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
channels[i] = 255d - channels[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int colorFromChannels(double[] channels, double alpha) {
|
|
||||||
int red = clampChannel(channels[0]);
|
|
||||||
int green = clampChannel(channels[1]);
|
|
||||||
int blue = clampChannel(channels[2]);
|
|
||||||
return Color.argb(clampChannel(alpha), red, green, blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Operand parseHexOperand(String raw) {
|
|
||||||
String value = raw != null ? raw.trim() : "";
|
String value = raw != null ? raw.trim() : "";
|
||||||
if (value.isEmpty() || value.charAt(0) != '#') {
|
if (value.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Expected colour literal");
|
return "";
|
||||||
}
|
}
|
||||||
String hex = value.substring(1);
|
int split = findTopLevelPipe(value);
|
||||||
if (hex.length() == 3 || hex.length() == 4) {
|
if (split < 0) {
|
||||||
hex = expandShortHex(hex);
|
return value;
|
||||||
} else if (hex.length() != 6 && hex.length() != 8) {
|
|
||||||
throw new IllegalArgumentException("Unsupported colour length");
|
|
||||||
}
|
}
|
||||||
boolean hasAlpha = hex.length() == 8;
|
return value.substring(0, split).trim() + " ; " + value.substring(split + 1).trim();
|
||||||
int offset = hasAlpha ? 2 : 0;
|
|
||||||
double alpha = 0d;
|
|
||||||
if (hasAlpha) {
|
|
||||||
alpha = parseHexByte(hex.substring(0, 2));
|
|
||||||
}
|
|
||||||
double[] rgb = new double[] {
|
|
||||||
parseHexByte(hex.substring(offset, offset + 2)),
|
|
||||||
parseHexByte(hex.substring(offset + 2, offset + 4)),
|
|
||||||
parseHexByte(hex.substring(offset + 4, offset + 6))
|
|
||||||
};
|
|
||||||
return new Operand(rgb, alpha, hasAlpha, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double parseHexByte(String value) {
|
private static int findTopLevelPipe(String value) {
|
||||||
return Integer.parseInt(value, 16);
|
boolean inVector = false;
|
||||||
}
|
int split = -1;
|
||||||
|
|
||||||
private static int clampChannel(double value) {
|
|
||||||
if (Double.isNaN(value) || Double.isInfinite(value)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (value <= 0d) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (value >= 255d) {
|
|
||||||
return 255;
|
|
||||||
}
|
|
||||||
return (int) Math.round(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean startsWithOperator(String value, int start) {
|
|
||||||
if (start < 0 || start >= value.length()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
char c = value.charAt(start);
|
|
||||||
return c == '+' || c == '-' || c == '*' || c == '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int findVariantSplit(String value) {
|
|
||||||
if (TextUtils.isEmpty(value)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
boolean quoted = false;
|
|
||||||
for (int i = 0; i < value.length(); i++) {
|
for (int i = 0; i < value.length(); i++) {
|
||||||
char c = value.charAt(i);
|
char c = value.charAt(i);
|
||||||
if (c == '\'') {
|
if (inVector) {
|
||||||
quoted = !quoted;
|
if (c == ']') {
|
||||||
|
inVector = false;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!quoted && c == '|') {
|
if (c == '[') {
|
||||||
return i;
|
inVector = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '|') {
|
||||||
|
if (split >= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
split = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return split;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDark(int color) {
|
private static String hexFor(int color) {
|
||||||
double brightness = (Color.red(color) * 299d
|
if (Color.alpha(color) >= 255) {
|
||||||
+ Color.green(color) * 587d
|
return String.format(java.util.Locale.ROOT, "#%06X", color & 0xFFFFFF);
|
||||||
+ Color.blue(color) * 114d) / 1000d;
|
|
||||||
return brightness < 128d;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String hexOperandPattern() {
|
|
||||||
return "#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LeadingColour {
|
|
||||||
final int color;
|
|
||||||
final int endIndex;
|
|
||||||
|
|
||||||
LeadingColour(int color, int endIndex) {
|
|
||||||
this.color = color;
|
|
||||||
this.endIndex = endIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Operand {
|
|
||||||
final double[] rgb;
|
|
||||||
final double alpha;
|
|
||||||
final boolean hasAlpha;
|
|
||||||
final boolean isHex;
|
|
||||||
|
|
||||||
Operand(double[] rgb, double alpha, boolean hasAlpha, boolean isHex) {
|
|
||||||
this.rgb = rgb;
|
|
||||||
this.alpha = alpha;
|
|
||||||
this.hasAlpha = hasAlpha;
|
|
||||||
this.isHex = isHex;
|
|
||||||
}
|
}
|
||||||
|
return String.format(java.util.Locale.ROOT, "#%08X", color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ package se.ajpanton.statusbartweak.shell.ui;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.UnderlineSpan;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
@@ -32,6 +36,7 @@ import com.google.android.material.switchmaterial.SwitchMaterial;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import se.ajpanton.statusbartweak.R;
|
import se.ajpanton.statusbartweak.R;
|
||||||
import se.ajpanton.statusbartweak.shell.settings.BatteryBarGeometry;
|
import se.ajpanton.statusbartweak.shell.settings.BatteryBarGeometry;
|
||||||
@@ -80,6 +85,8 @@ public final class BatteryBarFragment extends Fragment {
|
|||||||
TextView maxLevelLabel = root.findViewById(R.id.battery_bar_max_level_label);
|
TextView maxLevelLabel = root.findViewById(R.id.battery_bar_max_level_label);
|
||||||
MaterialButton defaultDischargeButton = root.findViewById(R.id.battery_bar_default_discharge_button);
|
MaterialButton defaultDischargeButton = root.findViewById(R.id.battery_bar_default_discharge_button);
|
||||||
MaterialButton defaultChargeButton = root.findViewById(R.id.battery_bar_default_charge_button);
|
MaterialButton defaultChargeButton = root.findViewById(R.id.battery_bar_default_charge_button);
|
||||||
|
TextView colourHelpToggle = root.findViewById(R.id.battery_bar_colour_help_toggle);
|
||||||
|
TextView colourHelp = root.findViewById(R.id.battery_bar_colour_help);
|
||||||
MaterialButton addThresholdButton = root.findViewById(R.id.battery_bar_add_threshold_button);
|
MaterialButton addThresholdButton = root.findViewById(R.id.battery_bar_add_threshold_button);
|
||||||
LinearLayout thresholdsContainer = root.findViewById(R.id.battery_bar_thresholds_container);
|
LinearLayout thresholdsContainer = root.findViewById(R.id.battery_bar_thresholds_container);
|
||||||
View geometrySection = root.findViewById(R.id.battery_bar_geometry_section);
|
View geometrySection = root.findViewById(R.id.battery_bar_geometry_section);
|
||||||
@@ -145,6 +152,11 @@ public final class BatteryBarFragment extends Fragment {
|
|||||||
SbtSettings.KEY_BATTERY_BAR_DEFAULT_CHARGE_COLOR,
|
SbtSettings.KEY_BATTERY_BAR_DEFAULT_CHARGE_COLOR,
|
||||||
SbtDefaults.BATTERY_BAR_DEFAULT_CHARGE_COLOR_DEFAULT),
|
SbtDefaults.BATTERY_BAR_DEFAULT_CHARGE_COLOR_DEFAULT),
|
||||||
R.string.battery_bar_colour_same_as_default);
|
R.string.battery_bar_colour_same_as_default);
|
||||||
|
bindExpandableHelp(
|
||||||
|
colourHelpToggle,
|
||||||
|
colourHelp,
|
||||||
|
R.string.battery_bar_colour_help_show,
|
||||||
|
R.string.battery_bar_colour_help_hide);
|
||||||
|
|
||||||
enabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
enabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
prefs.edit().putBoolean(SbtSettings.KEY_BATTERY_BAR_ENABLED, isChecked).apply();
|
prefs.edit().putBoolean(SbtSettings.KEY_BATTERY_BAR_ENABLED, isChecked).apply();
|
||||||
@@ -270,15 +282,129 @@ public final class BatteryBarFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String normalised = BatteryBarStyle.normalizeColor(colour);
|
String normalised = BatteryBarStyle.normalizeColor(colour);
|
||||||
String value;
|
CharSequence value;
|
||||||
|
int backgroundColor = Color.TRANSPARENT;
|
||||||
|
boolean hasBackground = false;
|
||||||
if (BatteryBarStyle.isThresholdChargeDefault(colour)) {
|
if (BatteryBarStyle.isThresholdChargeDefault(colour)) {
|
||||||
value = getString(R.string.battery_bar_colour_same_as_default);
|
value = getString(R.string.battery_bar_colour_same_as_default);
|
||||||
} else if (normalised.isEmpty()) {
|
} else if (normalised.isEmpty()) {
|
||||||
value = getString(emptyLabelRes);
|
value = getString(emptyLabelRes);
|
||||||
} else {
|
} else {
|
||||||
value = normalised;
|
value = displayColourValue(normalised);
|
||||||
|
backgroundColor = resolvePreviewColour(normalised);
|
||||||
|
hasBackground = true;
|
||||||
}
|
}
|
||||||
button.setText(getString(labelRes) + ": " + value);
|
SpannableStringBuilder label = new SpannableStringBuilder();
|
||||||
|
label.append(compactColourLabel(labelRes));
|
||||||
|
label.append(":\n");
|
||||||
|
label.append(value);
|
||||||
|
button.setText(label);
|
||||||
|
if (!hasBackground) {
|
||||||
|
button.setBackgroundTintList(null);
|
||||||
|
button.setTextColor(ContextCompat.getColor(requireContext(), R.color.sbt_on_surface));
|
||||||
|
} else {
|
||||||
|
button.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
|
||||||
|
button.setTextColor(contrastTextColor(backgroundColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String compactColourLabel(int labelRes) {
|
||||||
|
String label = getString(labelRes).toLowerCase(Locale.ROOT);
|
||||||
|
if (label.contains("discharge")) {
|
||||||
|
return getString(R.string.battery_bar_button_label_discharge);
|
||||||
|
}
|
||||||
|
return getString(R.string.battery_bar_button_label_charge);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence displayColourValue(String expression) {
|
||||||
|
String normalised = BatteryBarStyle.normalizeColor(expression);
|
||||||
|
if (normalised.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
SpannableStringBuilder out = new SpannableStringBuilder();
|
||||||
|
appendDisplayHex(out, resolveExpressionHex(normalised, Color.WHITE));
|
||||||
|
if (hasTintVariant(normalised)) {
|
||||||
|
out.append(" ; ");
|
||||||
|
appendDisplayHex(out, resolveExpressionHex(normalised, Color.BLACK));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendDisplayHex(SpannableStringBuilder out, String hex) {
|
||||||
|
int start = out.length();
|
||||||
|
out.append(hex);
|
||||||
|
if (hex != null && hex.length() == 9) {
|
||||||
|
out.setSpan(new UnderlineSpan(), start + 1, start + 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveExpressionHex(String expression, int referenceColor) {
|
||||||
|
int fallback = Color.WHITE;
|
||||||
|
int resolved = se.ajpanton.statusbartweak.shell.settings.ColourExpressionSupport.resolveColor(
|
||||||
|
expression,
|
||||||
|
referenceColor,
|
||||||
|
fallback,
|
||||||
|
true);
|
||||||
|
if (Color.alpha(resolved) >= 255) {
|
||||||
|
return String.format(Locale.ROOT, "#%06X", resolved & 0xFFFFFF);
|
||||||
|
}
|
||||||
|
return String.format(Locale.ROOT, "#%08X", resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasTintVariant(String expression) {
|
||||||
|
if (expression == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean inVector = false;
|
||||||
|
for (int i = 0; i < expression.length(); i++) {
|
||||||
|
char c = expression.charAt(i);
|
||||||
|
if (inVector) {
|
||||||
|
if (c == ']') {
|
||||||
|
inVector = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '[') {
|
||||||
|
inVector = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == ';' || c == '|') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolvePreviewColour(String expression) {
|
||||||
|
return se.ajpanton.statusbartweak.shell.settings.ColourExpressionSupport.resolveColor(
|
||||||
|
expression,
|
||||||
|
ContextCompat.getColor(requireContext(), R.color.sbt_on_surface),
|
||||||
|
Color.WHITE,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int contrastTextColor(int backgroundColor) {
|
||||||
|
int alpha = Color.alpha(backgroundColor);
|
||||||
|
int red = compositeChannel(Color.red(backgroundColor), alpha, 255);
|
||||||
|
int green = compositeChannel(Color.green(backgroundColor), alpha, 255);
|
||||||
|
int blue = compositeChannel(Color.blue(backgroundColor), alpha, 255);
|
||||||
|
double luminance = (0.299d * red + 0.587d * green + 0.114d * blue) / 255d;
|
||||||
|
return luminance > 0.55d ? Color.BLACK : Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int compositeChannel(int source, int alpha, int destination) {
|
||||||
|
return Math.round((source * alpha + destination * (255 - alpha)) / 255f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindExpandableHelp(TextView toggle, TextView content, int showRes, int hideRes) {
|
||||||
|
if (toggle == null || content == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toggle.setOnClickListener(v -> {
|
||||||
|
boolean show = content.getVisibility() != View.VISIBLE;
|
||||||
|
content.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
|
toggle.setText(show ? hideRes : showRes);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trimMappingStepButton(MaterialButton button) {
|
private void trimMappingStepButton(MaterialButton button) {
|
||||||
|
|||||||
@@ -570,6 +570,25 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="@string/battery_bar_default_charge_colour" />
|
android:text="@string/battery_bar_default_charge_colour" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/battery_bar_colour_help_toggle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/battery_bar_colour_help_show"
|
||||||
|
android:textAppearance="?attr/textAppearanceBody1"
|
||||||
|
android:textColor="@color/sbt_accent_dark"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/battery_bar_colour_help"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:text="@string/battery_bar_colour_help_text"
|
||||||
|
android:textAppearance="?attr/textAppearanceBody2" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
|||||||
@@ -165,11 +165,13 @@
|
|||||||
<string name="battery_bar_colours_title">Default colours</string>
|
<string name="battery_bar_colours_title">Default colours</string>
|
||||||
<string name="battery_bar_default_discharge_colour">Discharge colour</string>
|
<string name="battery_bar_default_discharge_colour">Discharge colour</string>
|
||||||
<string name="battery_bar_default_charge_colour">Charge colour</string>
|
<string name="battery_bar_default_charge_colour">Charge colour</string>
|
||||||
|
<string name="battery_bar_button_label_discharge">Discharge</string>
|
||||||
|
<string name="battery_bar_button_label_charge">Charge</string>
|
||||||
<string name="battery_bar_thresholds_title">Threshold colours</string>
|
<string name="battery_bar_thresholds_title">Threshold colours</string>
|
||||||
<string name="battery_bar_thresholds_hint">Below a threshold, the first matching colour overrides the default.</string>
|
<string name="battery_bar_thresholds_hint">Below a threshold, the first matching colour overrides the default.</string>
|
||||||
<string name="battery_bar_add_threshold">Add threshold</string>
|
<string name="battery_bar_add_threshold">Add threshold</string>
|
||||||
<string name="battery_bar_colour_dialog_title">Set colour</string>
|
<string name="battery_bar_colour_dialog_title">Set colour</string>
|
||||||
<string name="battery_bar_colour_dialog_hint">Examples: FA0, 80FA, or FFAA00</string>
|
<string name="battery_bar_colour_dialog_hint">Examples: #FA0, FA0, or #FA0 ; invRGB 0.5 *RGB</string>
|
||||||
<string name="battery_bar_threshold_percent_title">Threshold percent</string>
|
<string name="battery_bar_threshold_percent_title">Threshold percent</string>
|
||||||
<string name="battery_bar_threshold_percent_hint">Applies when battery is at or below this percent.</string>
|
<string name="battery_bar_threshold_percent_hint">Applies when battery is at or below this percent.</string>
|
||||||
<string name="battery_bar_threshold_change_percent">Change threshold %</string>
|
<string name="battery_bar_threshold_change_percent">Change threshold %</string>
|
||||||
@@ -179,7 +181,10 @@
|
|||||||
<string name="battery_bar_threshold_remove">Remove threshold</string>
|
<string name="battery_bar_threshold_remove">Remove threshold</string>
|
||||||
<string name="battery_bar_colour_auto">Auto</string>
|
<string name="battery_bar_colour_auto">Auto</string>
|
||||||
<string name="battery_bar_colour_same_as_discharge">Same as discharge</string>
|
<string name="battery_bar_colour_same_as_discharge">Same as discharge</string>
|
||||||
<string name="battery_bar_colour_same_as_default">Same as default</string>
|
<string name="battery_bar_colour_same_as_default">Default</string>
|
||||||
|
<string name="battery_bar_colour_help_show">Show colour syntax</string>
|
||||||
|
<string name="battery_bar_colour_help_hide">Hide colour syntax</string>
|
||||||
|
<string name="battery_bar_colour_help_text">Colour values:\n#FA0, #FFAA00, #80FFAA00, FA0, or [255, 255, 170, 0]\n\nPostfix math:\n#FA0 1.2 *RGB clip\n#FA0 invRGB\n\nOperators:\n+ - * / ^ inv min max sqrt clip dup\n\nChannels:\nAdd A, R, G, or B to limit vector operations.\nExamples: invRGB, *A, +RB\n\nTint variants:\nUse ; for a dark-tint expression.\nThe dark expression starts with the result from the bright expression.</string>
|
||||||
<string name="battery_bar_threshold_duplicate">A threshold at that percentage already exists.</string>
|
<string name="battery_bar_threshold_duplicate">A threshold at that percentage already exists.</string>
|
||||||
<string name="battery_bar_threshold_reordered">That threshold moved to a new position in the list.</string>
|
<string name="battery_bar_threshold_reordered">That threshold moved to a new position in the list.</string>
|
||||||
<string name="battery_bar_threshold_charge_note">Leave the field empty and press OK for \"Same as discharge\", or use the button below for \"Same as default\".</string>
|
<string name="battery_bar_threshold_charge_note">Leave the field empty and press OK for \"Same as discharge\", or use the button below for \"Same as default\".</string>
|
||||||
|
|||||||
+34
@@ -49,4 +49,38 @@ public final class ClockColorResolverTest {
|
|||||||
public void rejectsMoreThanTwoColorVariants() {
|
public void rejectsMoreThanTwoColorVariants() {
|
||||||
resolver.resolveColor(null, "#123 ; #456 ; #789", WHITE_REFERENCE, 0xffffffff);
|
resolver.resolveColor(null, "#123 ; #456 ; #789", WHITE_REFERENCE, 0xffffffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void batteryBarOptionsAllowBareHexColours() {
|
||||||
|
assertEquals(
|
||||||
|
0xff808080,
|
||||||
|
resolver.resolveColor(
|
||||||
|
null,
|
||||||
|
"808080",
|
||||||
|
WHITE_REFERENCE,
|
||||||
|
0xffffffff,
|
||||||
|
ClockColorResolver.Options.batteryBar()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void batteryBarOptionsKeepThreeDigitNumbersAsNumbers() {
|
||||||
|
assertEquals(
|
||||||
|
0xffffffff,
|
||||||
|
resolver.resolveColor(
|
||||||
|
null,
|
||||||
|
"255",
|
||||||
|
WHITE_REFERENCE,
|
||||||
|
0xffffffff,
|
||||||
|
ClockColorResolver.Options.batteryBar()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void batteryBarOptionsRejectBatteryBarOperand() {
|
||||||
|
resolver.resolveColor(
|
||||||
|
null,
|
||||||
|
"#batterybar",
|
||||||
|
WHITE_REFERENCE,
|
||||||
|
0xffffffff,
|
||||||
|
ClockColorResolver.Options.batteryBar());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user