Add the Orchid theme: pink letters, violet alternates, violet activated keys

Harley wanted a pink/violet keyboard. The alternates use the exact violet sampled
from his own UI accent (#c67dff); letters are pink (#ff8ad8), activated keys
(shift/Esc/123+) are filled violet (#a855f7) with dark glyphs, caps-lock is
periwinkle blue (#9db8ff), background is violet-black (#100a1c). Contrast checked:
9.13 / 7.19 / 9.92 / 4.9 :1. Five edits: themes.xml style, arrays.xml entries+values
(kept index-aligned), strings.xml label, Config.java case.
This commit is contained in:
nyra
2026-09-27 18:07:05 +00:00
parent 08c895aa12
commit 31eecc2a13
7 changed files with 100 additions and 5 deletions
+2
View File
@@ -45,6 +45,7 @@
<item>@string/pref_theme_e_everforestlight</item>
<item>@string/pref_theme_e_cobalt</item>
<item>@string/pref_theme_e_pine</item>
<item>@string/pref_theme_e_orchid</item>
<item>@string/pref_theme_e_epaperblack</item>
</string-array>
<string-array name="pref_theme_values">
@@ -64,6 +65,7 @@
<item>everforestlight</item>
<item>cobalt</item>
<item>pine</item>
<item>orchid</item>
<item>epaperblack</item>
</string-array>
<string-array name="pref_swipe_dist_entries">
+1
View File
@@ -85,6 +85,7 @@
<string name="pref_theme_e_everforestlight">Everforest Light</string>
<string name="pref_theme_e_cobalt">Cobalt</string>
<string name="pref_theme_e_pine">Pine</string>
<string name="pref_theme_e_orchid">Orchid</string>
<string name="pref_theme_e_epaperblack">ePaper Black</string>
<string name="pref_swipe_dist_e_very_short">Very short</string>
<string name="pref_swipe_dist_e_short">Short</string>
+19
View File
@@ -311,6 +311,25 @@
<item name="emoji_color">?colorLabel</item>
<item name="clipboard_divider_color">?colorKey</item>
</style>
<style name="Orchid" parent="BaseTheme">
<item name="android:isLightTheme">false</item>
<item name="colorKeyboard">#100a1c</item>
<item name="colorKey">?colorKeyboard</item>
<item name="colorKeyActivated">#a855f7</item>
<item name="colorLabel">#ff8ad8</item>
<item name="colorLabelActivated">?colorKey</item>
<item name="colorLabelLocked">#9db8ff</item>
<item name="colorSubLabel">#c67dff</item>
<item name="keyBorderWidth">0.0dip</item>
<item name="keyBorderWidthActivated">0.0dip</item>
<item name="keyBorderColorLeft">#241734</item>
<item name="keyBorderColorTop">#241734</item>
<item name="keyBorderColorRight">#241734</item>
<item name="keyBorderColorBottom">#241734</item>
<item name="emoji_button_bg">?colorKeyboard</item>
<item name="emoji_color">?colorLabel</item>
<item name="clipboard_divider_color">?colorKey</item>
</style>
<style name="ePaperBlack" parent="BaseTheme">
<item name="android:isLightTheme">false</item>
<item name="colorKeyboard">#000000</item>
+1
View File
@@ -282,6 +282,7 @@ public final class Config
case "everforestlight": return R.style.EverforestLight;
case "cobalt": return R.style.Cobalt;
case "pine": return R.style.Pine;
case "orchid": return R.style.Orchid;
case "epaperblack": return R.style.ePaperBlack;
default:
case "system":
+4 -1
View File
@@ -426,8 +426,11 @@ public class Keyboard2View extends View
GAUGE_PAINT.setTextSize(size);
GAUGE_PAINT.setTypeface(android.graphics.Typeface.DEFAULT);
GAUGE_PAINT.setTextAlign(Paint.Align.CENTER);
// Stale hides the number behind a dash; a partial day keeps the number but dims it, so a
// floor can never be read as the day's total. A low balance stays red: that reading is live.
int labelColor = stale ? 0xff92949a :
(UsageGauge.isLowBalance(reading) ? 0xffe53935 : _theme.labelColor);
(UsageGauge.isLowBalance(reading) ? 0xffe53935 :
(reading.partial ? 0xff92949a : _theme.labelColor));
if (!stale)
{
int luminance = (android.graphics.Color.red(labelColor) * 299
+47 -4
View File
@@ -18,7 +18,9 @@ public final class UsageGauge
// without adding a character to the generated compose table.
public static final String KEY_MARKER = "usage_gauge";
public static final long REFRESH_AFTER_MS = 5 * 60 * 1000L;
public static final long STALE_AFTER_MS = 15 * 60 * 1000L;
// The sampler publishes every 15 minutes, so one missed tick must not read as stale: three
// do. A threshold equal to the interval makes the gauge blink to '--' on every cycle.
public static final long STALE_AFTER_MS = 45 * 60 * 1000L;
public static final class Reading
{
@@ -26,13 +28,25 @@ public final class UsageGauge
public final double weeklyPct;
public final double spentTodayUsd;
public final double balance;
/** True when the server's spend window opened mid-day: the total is a floor, not the day. */
public final boolean partial;
/** True when the server says the reading it sent is too old to present as current. */
public final boolean serverStale;
Reading(double h5, double weekly, double spent, double balance_)
{
this(h5, weekly, spent, balance_, false, false);
}
Reading(double h5, double weekly, double spent, double balance_, boolean partial_,
boolean serverStale_)
{
h5Pct = h5;
weeklyPct = weekly;
spentTodayUsd = spent;
balance = balance_;
partial = partial_;
serverStale = serverStale_;
}
}
@@ -63,7 +77,10 @@ public final class UsageGauge
if (!finite(h5) || !finite(weekly) || !finite(spent) || !finite(balance)
|| h5 < 0 || weekly < 0 || spent < 0)
return null;
return new Reading(h5, weekly, spent, balance);
boolean partial = deepseek.optBoolean("partial", false);
boolean serverStale = codex.optBoolean("stale", false)
|| deepseek.optBoolean("stale", false);
return new Reading(h5, weekly, spent, balance, partial, serverStale);
}
catch (RuntimeException e)
{
@@ -90,7 +107,9 @@ public final class UsageGauge
if (!finite(h5) || !finite(weekly) || !finite(spent) || !finite(balance)
|| h5 < 0 || weekly < 0 || spent < 0)
return null;
return new Reading(h5, weekly, spent, balance);
boolean partial = booleanForKey(deepseek, "partial");
boolean serverStale = booleanForKey(codex, "stale") || booleanForKey(deepseek, "stale");
return new Reading(h5, weekly, spent, balance, partial, serverStale);
}
catch (Exception e)
{
@@ -135,6 +154,28 @@ public final class UsageGauge
return Double.parseDouble(matcher.group(1));
}
/** Only a literal true counts: an absent or malformed flag must never claim confidence. */
private static boolean booleanForKey(String json, String key)
{
try
{
int at = json.indexOf("\"" + key + "\"");
if (at < 0)
return false;
int colon = json.indexOf(':', at);
if (colon < 0)
return false;
int i = colon + 1;
while (i < json.length() && Character.isWhitespace(json.charAt(i)))
i++;
return json.startsWith("true", i);
}
catch (Exception e)
{
return false;
}
}
private static double requiredNumber(JSONObject object, String key) throws Exception
{
Object value = object.get(key);
@@ -180,7 +221,9 @@ public final class UsageGauge
Reading reading = fetch(url, token);
synchronized (UsageGauge.class)
{
if (reading == null)
// A reading the server itself flags as too old is as unusable as a failed fetch:
// presenting it as current is the exact lie the gauge is built to avoid.
if (reading == null || reading.serverStale)
_failed = true;
else
{
+26
View File
@@ -56,4 +56,30 @@ public class UsageGaugeTest
assertFalse(UsageGauge.isLowBalance(UsageGauge.parse(
RESPONSE.replace("0.99", "1.00"))));
}
@Test
public void aPartialDayKeepsTheNumberButFlagsIt()
{
UsageGauge.Reading partial = UsageGauge.parse(RESPONSE.replace(
"\"balance\":0.99}", "\"balance\":0.99,\"partial\":true}"));
assertTrue(partial.partial);
assertFalse(UsageGauge.parse(RESPONSE).partial);
}
@Test
public void aReadingTheServerCallsStaleIsNotPresentedAsCurrent()
{
UsageGauge.Reading stale = UsageGauge.parse(RESPONSE.replace(
"\"balance\":0.99}", "\"balance\":0.99,\"stale\":true}"));
assertTrue(stale.serverStale);
assertFalse(UsageGauge.parse(RESPONSE).serverStale);
}
@Test
public void oneMissedSampleIsNotStaleButThreeAre()
{
long now = 1000000000000L;
assertFalse(UsageGauge.isStale(now - 20 * 60 * 1000L, now, false, "tok"));
assertTrue(UsageGauge.isStale(now - 50 * 60 * 1000L, now, false, "tok"));
}
}