diff --git a/app/src/main/assets/i18n/es.tsv b/app/src/main/assets/i18n/es.tsv
index 145d5a7..4456d7a 100644
--- a/app/src/main/assets/i18n/es.tsv
+++ b/app/src/main/assets/i18n/es.tsv
@@ -1166,3 +1166,5 @@ Dimmed tools need Shizuku. Start it on the first tab. Las herramientas atenuadas
Create work profile? ¿Crear perfil de trabajo?
This adds a second, separate space to your phone. Your own apps and data stay as they are. You can remove it later. Esto añade un espacio aparte en tu teléfono. Tus apps y datos no cambian. Puedes quitarlo después.
Create Crear
+Light theme Tema claro
+Use a light background instead of OLED black. Usa un fondo claro en lugar del negro OLED.
diff --git a/app/src/main/java/io/github/order52/tinykit/MainActivity.java b/app/src/main/java/io/github/order52/tinykit/MainActivity.java
index a444f32..8497a40 100644
--- a/app/src/main/java/io/github/order52/tinykit/MainActivity.java
+++ b/app/src/main/java/io/github/order52/tinykit/MainActivity.java
@@ -251,6 +251,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) {
private volatile List apps;
private final Object appsLock = new Object();
private SharedPreferences prefs;
+ private boolean lightTheme;
private FrameLayout frame;
private final TextView[] tabs = new TextView[TAB_NAMES.length];
private final ImageView[] tabIcons = new ImageView[TAB_NAMES.length];
@@ -293,11 +294,33 @@ public void onReceive(Context context, Intent intent) {
}
};
+ private void applySystemBarColors() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ int flags = getWindow().getDecorView().getSystemUiVisibility();
+ if (lightTheme) {
+ flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ }
+ } else {
+ flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ }
+ }
+ getWindow().getDecorView().setSystemUiVisibility(flags);
+ }
+ }
+
@Override
@android.annotation.SuppressLint("UnspecifiedRegisterReceiverFlag") // flag exists from Android 13
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
prefs = getSharedPreferences("tinykit", MODE_PRIVATE);
+ lightTheme = prefs.getBoolean("light_theme", false);
+ if (lightTheme) setTheme(R.style.AppTheme_Light);
+ super.onCreate(savedInstanceState);
+ Tr.init(this);
+ applySystemBarColors();
// Warm the installed-apps cache (loading labels is the slow part) so every per-app list,
// picker and log opens instantly. Runs off the worker queue to not delay the first tap.
thumbs.execute(this::installedApps);
@@ -580,7 +603,7 @@ public void onBackPressed() {
*/
private View buildTabBar() {
LinearLayout bar = new LinearLayout(this);
- bar.setBackgroundColor(0xFF0D0D0D);
+ bar.setBackgroundColor(lightTheme ? 0xFFEBEBEF : 0xFF0D0D0D);
bar.setMinimumHeight(dp(80));
for (int i = 0; i < TAB_NAMES.length; i++) {
LinearLayout tab = vertical();
@@ -596,7 +619,8 @@ private View buildTabBar() {
TextView label = text(TAB_NAMES[i], 12, false);
label.setGravity(Gravity.CENTER);
label.setSingleLine(true);
- label.setPadding(dp(2), dp(4), dp(2), 0);
+ label.setEllipsize(android.text.TextUtils.TruncateAt.END);
+ label.setPadding(dp(1), dp(4), dp(1), 0);
label.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
tabs[i] = label;
tab.addView(label, new LinearLayout.LayoutParams(-1, -2));
@@ -613,7 +637,7 @@ private View buildTabBar() {
private void fitTabs() {
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics());
- float min = px * 0.6f;
+ float min = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10.5f, getResources().getDisplayMetrics());
android.text.TextPaint paint = new android.text.TextPaint(tabs[0].getPaint());
paint.setTypeface(Typeface.DEFAULT_BOLD); // the selected tab is bold, and widest
while (px > min && !tabsFit(paint, px)) px -= 0.5f;
@@ -623,7 +647,7 @@ private void fitTabs() {
private boolean tabsFit(android.text.TextPaint paint, float px) {
paint.setTextSize(px);
for (TextView tab : tabs) {
- int room = tab.getWidth() - tab.getPaddingLeft() - tab.getPaddingRight() - dp(4);
+ int room = tab.getWidth() - tab.getPaddingLeft() - tab.getPaddingRight() - dp(2);
if (room <= 0) return true; // not laid out yet
if (paint.measureText(tab.getText().toString()) > room) return false;
}
@@ -634,12 +658,12 @@ private boolean tabsFit(android.text.TextPaint paint, float px) {
private android.graphics.drawable.RippleDrawable pill(boolean selected) {
GradientDrawable fill = new GradientDrawable();
fill.setCornerRadius(dp(16));
- fill.setColor(selected ? 0x338C9EFF : 0);
+ fill.setColor(selected ? (lightTheme ? 0x262962FF : 0x338C9EFF) : 0);
GradientDrawable mask = new GradientDrawable();
mask.setCornerRadius(dp(16));
- mask.setColor(0xFF000000);
+ mask.setColor(lightTheme ? 0xFFFFFFFF : 0xFF000000);
return new android.graphics.drawable.RippleDrawable(
- android.content.res.ColorStateList.valueOf(0x4DFFFFFF), fill, mask);
+ android.content.res.ColorStateList.valueOf(lightTheme ? 0x20000000 : 0x4DFFFFFF), fill, mask);
}
private View buildTab(int i) {
@@ -671,10 +695,10 @@ private void selectTab(int index) {
int accent = attrColor(android.R.attr.colorAccent);
for (int i = 0; i < tabs.length; i++) {
boolean selected = i == index;
- tabs[i].setTextColor(selected ? accent : 0xFFFFFFFF);
- tabs[i].setAlpha(selected ? 1f : 0.65f);
+ tabs[i].setTextColor(selected ? accent : (lightTheme ? 0xFF111111 : 0xFFFFFFFF));
+ tabs[i].setAlpha(selected ? 1f : (lightTheme ? 1f : 0.65f));
tabs[i].setTypeface(selected ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
- tabIcons[i].setColorFilter(selected ? accent : 0xA6FFFFFF);
+ tabIcons[i].setColorFilter(selected ? accent : (lightTheme ? 0xFF111111 : 0xA6FFFFFF));
tabIcons[i].setBackground(pill(selected));
((View) tabs[i].getParent()).setSelected(selected);
}
@@ -737,7 +761,7 @@ private void refreshShizuku() {
tokenText.setText(Tr.t(token().isEmpty()
? "Not set. Tap to paste it from Shizuku's settings."
: "Set. Start and Stop go straight to Shizuku."));
- for (View row : shizukuRows) row.setAlpha(ready ? 1f : 0.4f);
+ for (View row : shizukuRows) row.setAlpha(ready ? 1f : (lightTheme ? 1f : 0.4f));
for (View label : shizukuLabels) label.setVisibility(ready ? View.GONE : View.VISIBLE);
if (ready) {
loadSettings();
@@ -937,7 +961,8 @@ private View buildShizuku() {
status.setPadding(dp(16), dp(16), dp(16), dp(12));
GradientDrawable card = new GradientDrawable();
card.setCornerRadius(dp(16));
- card.setColor(0xFF121212);
+ card.setColor(lightTheme ? 0xFFFFFFFF : 0xFF121212);
+ if (lightTheme) card.setStroke(dp(1), 0x1A000000);
status.setBackground(card);
shizukuText = text("", 18, true);
shizukuText.setGravity(Gravity.CENTER_VERTICAL);
@@ -964,7 +989,7 @@ private View buildShizuku() {
TextView help = text("Start uses the auth token when it's set, otherwise Termux with "
+ "shizuku-termux, otherwise it opens Shizuku so you tap its Start button. Stop closes "
+ "Shizuku fully, so a Watchdog can't start it straight back up.", 13, false);
- help.setAlpha(0.6f);
+ setDim(help, 0.6f);
help.setPadding(dp(16), dp(8), dp(16), 0);
list.addView(help);
@@ -990,7 +1015,13 @@ private EditText searchField(String hint) {
search.setSingleLine(true);
search.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
search.setPadding(dp(16), dp(10), dp(16), dp(10));
- search.setBackground(roundRect(0xFF1A1A1A, 24));
+ if (lightTheme) {
+ search.setTextColor(0xFF000000);
+ search.setHintTextColor(0xFF555555);
+ }
+ GradientDrawable bg = roundRect(lightTheme ? 0xFFFFFFFF : 0xFF1A1A1A, 24);
+ if (lightTheme) bg.setStroke(dp(1), 0x26000000);
+ search.setBackground(bg);
return search;
}
@@ -1009,9 +1040,14 @@ private View withSearch(View home) {
search.setImeOptions(android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH);
search.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
search.setPadding(dp(16), dp(12), dp(16), dp(12));
+ if (lightTheme) {
+ search.setTextColor(0xFF000000);
+ search.setHintTextColor(0xFF555555);
+ }
GradientDrawable box = new GradientDrawable();
box.setCornerRadius(dp(24));
- box.setColor(0xFF1A1A1A);
+ box.setColor(lightTheme ? 0xFFFFFFFF : 0xFF1A1A1A);
+ if (lightTheme) box.setStroke(dp(1), 0x26000000);
search.setBackground(box);
LinearLayout.LayoutParams boxMargin = new LinearLayout.LayoutParams(-1, -2);
boxMargin.setMargins(dp(12), dp(8), dp(12), dp(4));
@@ -1115,7 +1151,7 @@ private void reveal(Feature f) {
f.row.post(() -> {
if (sv != null) sv.smoothScrollTo(0, Math.max(0, f.row.getTop() - dp(64)));
Drawable old = f.row.getBackground();
- f.row.setBackgroundColor(0x408C9EFF);
+ f.row.setBackgroundColor(lightTheme ? 0x262962FF : 0x408C9EFF);
main.postDelayed(() -> f.row.setBackground(old), 1400);
});
}
@@ -1384,6 +1420,16 @@ private View buildInfo() {
link(list, "Crash log", "See why an app crashed or closed by itself.", this::openCrashes);
section(list, "TinyKit");
+ Switch lightThemeSwitch = new Switch(this);
+ lightThemeSwitch.setChecked(lightTheme);
+ lightThemeSwitch.setClickable(false);
+ lightThemeSwitch.setFocusable(false);
+ row(list, "Light theme", "Use a light background instead of OLED black.", lightThemeSwitch, () -> {
+ boolean on = !lightThemeSwitch.isChecked();
+ lightThemeSwitch.setChecked(on);
+ prefs.edit().putBoolean("light_theme", on).apply();
+ recreate();
+ }, false);
// The one switch for everything TinyKit may keep running. Off: it never runs by itself.
Switch backgroundSwitch = new Switch(this);
backgroundSwitch.setClickable(false);
@@ -1591,7 +1637,7 @@ private void openShutUp() {
((TextView) choose[0].getTag()).setText(Tr.t(n == 0 ? "No app chosen yet."
: n + (n == 1 ? " app chosen." : " apps chosen.")));
boolean paused = on && !background;
- status.setTextColor(paused ? attrColor(android.R.attr.colorAccent) : 0xB3FFFFFF);
+ status.setTextColor(paused ? attrColor(android.R.attr.colorAccent) : (lightTheme ? 0xFF000000 : 0xB3FFFFFF));
status.setText(Tr.t(BackgroundService.shutUpRunning()
? (ShutUp.hidden(prefs) ? "Running. Hidden right now, because a chosen app is open."
: "Running. Nothing is hidden until you open a chosen app.")
@@ -1817,6 +1863,7 @@ private static final class Pkg {
}
private static final int[] LEVEL_COLORS = {0xFF66BB6A, 0xFFFFCA28, 0xFFFF8A65, 0xFFEF5350};
+ private static final int[] LEVEL_COLORS_LIGHT = {0xFF1B5E20, 0xFFB25E00, 0xFFC2410C, 0xFFB71C1C};
/** Apps in use right now, with why: removing one would break the phone. Call off the main thread. */
private Map inUse() {
@@ -1904,7 +1951,7 @@ private void openDebloat() {
TextView help = text("Remove apps your phone came with, for you only. Nothing is deleted from the "
+ "system: bring any app back from Removed, and a factory reset restores all of them. "
+ "Hold an app to read about it.", 13, false);
- help.setAlpha(0.8f);
+ setDim(help, 0.8f);
page.addView(help);
EditText search = searchField("Search apps");
page.addView(search, fieldMargin());
@@ -1927,8 +1974,20 @@ private void openDebloat() {
Adapter adapter = new Adapter<>(true, (p, row) -> {
row.first.setText(Tr.t(p.label));
row.second.setText(Tr.t(p.pkg));
- row.third.setText(Tr.t(Bloat.LEVELS[p.level] + " · " + p.note));
- row.third.setTextColor(LEVEL_COLORS[p.level]);
+ if (lightTheme) {
+ String levelStr = Tr.t(Bloat.LEVELS[p.level]);
+ String noteStr = Tr.t(p.note);
+ android.text.SpannableStringBuilder sb = new android.text.SpannableStringBuilder();
+ sb.append(levelStr);
+ sb.setSpan(new android.text.style.ForegroundColorSpan(LEVEL_COLORS_LIGHT[p.level]),
+ 0, levelStr.length(), android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ sb.append(" · ").append(noteStr);
+ row.third.setTextColor(0xFF222222);
+ row.third.setText(sb);
+ } else {
+ row.third.setText(Tr.t(Bloat.LEVELS[p.level]) + " · " + Tr.t(p.note));
+ row.third.setTextColor(LEVEL_COLORS[p.level]);
+ }
row.check.setChecked(p.checked);
row.check.setVisibility(p.locked ? View.INVISIBLE : View.VISIBLE);
showIcon(row, p.pkg);
@@ -1995,8 +2054,8 @@ private void openDebloat() {
Pkg p = adapter.getItem(position);
new AlertDialog.Builder(this)
.setTitle(Tr.t(p.label))
- .setMessage(Tr.t(Bloat.LEVELS[p.level] + ": " + Bloat.MEANING[p.level] + "\n\n" + p.note
- + "\n\n" + p.pkg + (versionName(p.pkg).isEmpty() ? "" : "\nVersion " + versionName(p.pkg))))
+ .setMessage(Tr.t(Bloat.LEVELS[p.level]) + ": " + Tr.t(Bloat.MEANING[p.level]) + "\n\n" + Tr.t(p.note)
+ + "\n\n" + p.pkg + (versionName(p.pkg).isEmpty() ? "" : "\n" + Tr.t("Version ") + versionName(p.pkg)))
.setPositiveButton(Tr.t("OK"), null)
.show();
return true;
@@ -2237,7 +2296,7 @@ private void openInstallHistory() {
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Apps installed with TinyKit's installer, newest first. Tap one to open or "
+ "remove it. This list is read from Android's own records, so TinyKit stores nothing.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
TextView loading = text("Reading…", 14, false);
page.addView(loading);
@@ -2299,10 +2358,10 @@ private String installOptionsSummary() {
StringBuilder on = new StringBuilder();
String[] short_ = {"Allow downgrade", "Grant all permissions", "Test-only apps"};
for (int i = 0; i < INSTALL_OPTION_BITS.length; i++) {
- if ((options & INSTALL_OPTION_BITS[i]) != 0) on.append(on.length() > 0 ? ", " : "").append(short_[i]);
+ if ((options & INSTALL_OPTION_BITS[i]) != 0) on.append(on.length() > 0 ? ", " : "").append(Tr.t(short_[i]));
}
return on.length() == 0 ? "Normal install. Tap to allow downgrades or grant permissions."
- : "On: " + on + ".";
+ : Tr.t("On: ") + on + ".";
}
private void chooseInstallOptions(TextView summary) {
@@ -2335,7 +2394,7 @@ private void openTrackers() {
TextView help = text("Ad and analytics libraries found inside your apps, most first. This is what each "
+ "app declares, not proof it uses them. Tap an app to see them" + (FirewallPolicy.supported()
? " or cut it off the internet." : "."), 13, false);
- help.setAlpha(0.8f);
+ setDim(help, 0.8f);
page.addView(help);
CheckBox system = new CheckBox(this);
system.setText(Tr.t("Show system apps"));
@@ -2444,7 +2503,7 @@ private void openChannels(String pkg) {
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Tick = the app may show this kind of notification. Untick to block just that kind. "
+ "Some system channels can't be blocked.", 13, false);
- help.setAlpha(0.8f);
+ setDim(help, 0.8f);
page.addView(help);
TextView loading = text("Reading…", 14, false);
page.addView(loading);
@@ -2516,7 +2575,7 @@ private void openScreenTime() {
page.addView(total);
TextView help = text("How long each app was on your screen, and how often you opened it. Read from "
+ "Android's usage history, so TinyKit measures nothing itself.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
TextView loading = text("Reading usage…", 14, false);
page.addView(loading);
@@ -2707,7 +2766,7 @@ private void openPicker(String title, String help, boolean checkable, Loader loa
LinearLayout page = vertical();
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView helpView = text(help, 14, false);
- helpView.setAlpha(0.8f);
+ setDim(helpView, 0.8f);
page.addView(helpView);
EditText search = searchField("Search apps");
page.addView(search, fieldMargin());
@@ -2922,7 +2981,7 @@ private void openRunning() {
TextView summary = text("Loading…", 15, true);
page.addView(summary);
TextView help = text("Tap an app to stop it. System processes can't be stopped.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
ListView listView = new ListView(this);
page.addView(listView, new LinearLayout.LayoutParams(-1, 0, 1f));
@@ -2952,8 +3011,8 @@ private void openRunning() {
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
((ActivityManager) getSystemService(ACTIVITY_SERVICE)).getMemoryInfo(mem);
main.post(() -> {
- summary.setText(Tr.t("Memory: " + Formatter.formatShortFileSize(this, mem.totalMem - mem.availMem)
- + " used of " + Formatter.formatShortFileSize(this, mem.totalMem)));
+ summary.setText(Tr.t("Memory: ") + Formatter.formatShortFileSize(this, mem.totalMem - mem.availMem)
+ + Tr.t(" used of ") + Formatter.formatShortFileSize(this, mem.totalMem));
procs.clear();
procs.addAll(found);
apply.run();
@@ -3034,7 +3093,7 @@ private void infoPage(String title, InfoJob job) {
continue;
}
TextView key = text(item[0], 13, false);
- key.setAlpha(0.7f);
+ setDim(key, 0.7f);
key.setPadding(0, dp(8), 0, 0);
page.addView(key);
TextView value = text(item[1], 16, false);
@@ -3430,7 +3489,7 @@ private void openRecover() {
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Files found in every recycle bin and trash, with thumbnails. "
+ "Tap one to restore it to Download/TinyKit/Recovered. Largest first.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
TextView loading = text("Searching…", 14, false);
page.addView(loading);
@@ -3581,7 +3640,7 @@ public View getView(int position, View convertView, ViewGroup parent) {
name.setMaxLines(1);
name.setEllipsize(TextUtils.TruncateAt.MIDDLE);
sub = text("", 12, false);
- sub.setAlpha(0.65f);
+ setDim(sub, 0.65f);
texts.addView(name);
texts.addView(sub);
line.addView(texts, new LinearLayout.LayoutParams(0, -2, 1f));
@@ -4176,7 +4235,7 @@ private void openNetworkInfo() {
/** Adds a dim key line and a selectable value line, returning the value view so it can update. */
private TextView addKv(LinearLayout page, String key, String value) {
TextView k = text(key, 13, false);
- k.setAlpha(0.7f);
+ setDim(k, 0.7f);
k.setPadding(0, dp(8), 0, 0);
page.addView(k);
TextView v = text(value, 16, false);
@@ -4483,7 +4542,7 @@ private void rowsPage(String title, String help, String empty, InfoJob job) {
LinearLayout page = vertical();
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView helpView = text(help, 13, false);
- helpView.setAlpha(0.7f);
+ setDim(helpView, 0.7f);
page.addView(helpView);
TextView loading = text("Loading…", 14, false);
page.addView(loading);
@@ -4574,7 +4633,7 @@ private void openAppList(String title, String help, OnText onPick) {
LinearLayout page = vertical();
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView helpView = text(help, 14, false);
- helpView.setAlpha(0.8f);
+ setDim(helpView, 0.8f);
page.addView(helpView);
EditText search = searchField("Search apps");
page.addView(search, fieldMargin());
@@ -4653,7 +4712,7 @@ private void openAppPage(String pkg) {
LinearLayout htext = vertical();
htext.addView(text(label(pkg), 20, true));
TextView sub = text(pkg, 12, false);
- sub.setAlpha(0.6f);
+ setDim(sub, 0.6f);
htext.addView(sub);
head.addView(htext);
list.addView(head);
@@ -4742,9 +4801,9 @@ private void opRow(LinearLayout list, String pkg, String[] op, boolean permissio
private void stylePill(TextView pill, String key, boolean selected) {
GradientDrawable shape = new GradientDrawable();
shape.setCornerRadius(dp(8));
- shape.setColor(selected ? stateColor(key) : 0xFF1C1C1C);
+ shape.setColor(selected ? stateColor(key) : (lightTheme ? 0xFFE0E0E0 : 0xFF1C1C1C));
pill.setBackground(shape);
- pill.setTextColor(selected ? 0xFF000000 : 0xFFBDBDBD);
+ pill.setTextColor(selected ? 0xFF000000 : (lightTheme ? 0xFF000000 : 0xFFBDBDBD));
}
private int stateColor(String state) {
@@ -5174,7 +5233,7 @@ private void openWorkApps(int id) {
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Tap an app to open it, freeze it, set it to freeze by itself, or add a launcher "
+ "shortcut. A frozen app is off and keeps its data.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
EditText search = searchField("Search apps");
page.addView(search, fieldMargin());
@@ -5418,7 +5477,7 @@ private void openUnused() {
LinearLayout page = vertical();
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Longest-unused apps first. Tap one to open its settings.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
TextView loading = text("Reading usage…", 14, false);
page.addView(loading);
@@ -5574,7 +5633,7 @@ private void openRecentAccess() {
LinearLayout page = vertical();
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Most recent first, from the system's app-ops record.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
TextView loading = text("Reading…", 14, false);
page.addView(loading);
@@ -5660,7 +5719,7 @@ private void openMixedAudio() {
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Let apps play audio together, or mute an app. These are app-op changes; "
+ "nothing runs in the background.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
EditText search = searchField("Search apps");
page.addView(search, fieldMargin());
@@ -5870,7 +5929,7 @@ private void showPrivacyState(View row, Switch view, TextView sub, String summar
row.setEnabled(false);
view.setEnabled(false);
shizukuRows.remove(row);
- row.setAlpha(0.4f);
+ row.setAlpha(lightTheme ? 0.85f : 0.4f);
sub.setText(Tr.t(summary + " · Not on this phone"));
} else {
view.setChecked(state);
@@ -5940,7 +5999,7 @@ private void openWifi() {
LinearLayout page = vertical();
page.setPadding(dp(16), dp(12), dp(16), 0);
TextView help = text("Tap a network to see its password and a QR code to share it.", 13, false);
- help.setAlpha(0.7f);
+ setDim(help, 0.7f);
page.addView(help);
EditText search = searchField("Search networks");
page.addView(search, fieldMargin());
@@ -6365,7 +6424,7 @@ private View tileGrid(List tiles, int[] grid, int page, String picked, O
View dot = new View(this);
GradientDrawable shape = new GradientDrawable();
shape.setShape(GradientDrawable.OVAL);
- shape.setColor(p == page ? 0xFFFFFFFF : 0x55FFFFFF);
+ shape.setColor(p == page ? (lightTheme ? 0xFF000000 : 0xFFFFFFFF) : (lightTheme ? 0x40000000 : 0x55FFFFFF));
dot.setBackground(shape);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp(7), dp(7));
params.setMargins(dp(4), 0, dp(4), 0);
@@ -6425,9 +6484,9 @@ private View roundTile(QsTile tile, boolean picked) {
FrameLayout circle = new FrameLayout(this);
GradientDrawable shape = new GradientDrawable();
shape.setShape(GradientDrawable.OVAL);
- shape.setColor(picked ? attrColor(android.R.attr.colorAccent) : 0xFF262626);
+ shape.setColor(picked ? attrColor(android.R.attr.colorAccent) : (lightTheme ? 0xFFE0E0E4 : 0xFF262626));
circle.setBackground(shape);
- circle.addView(tileIcon(tile), new FrameLayout.LayoutParams(dp(24), dp(24), Gravity.CENTER));
+ circle.addView(tileIcon(tile, picked), new FrameLayout.LayoutParams(dp(24), dp(24), Gravity.CENTER));
cell.addView(circle, new LinearLayout.LayoutParams(dp(52), dp(52)));
TextView label = text(tile.title, 12, false);
label.setGravity(Gravity.CENTER_HORIZONTAL);
@@ -6446,12 +6505,12 @@ private View wideTile(QsTile tile, boolean picked) {
cell.setPadding(dp(16), 0, dp(12), 0);
GradientDrawable shape = new GradientDrawable();
shape.setCornerRadius(dp(24));
- shape.setColor(picked ? attrColor(android.R.attr.colorAccent) : 0xFF262626);
+ shape.setColor(picked ? attrColor(android.R.attr.colorAccent) : (lightTheme ? 0xFFE0E0E4 : 0xFF262626));
cell.setBackground(shape);
cell.setForeground(getDrawable(attrResource(android.R.attr.selectableItemBackground)));
LinearLayout.LayoutParams iconSize = new LinearLayout.LayoutParams(dp(20), dp(20));
iconSize.setMargins(0, 0, dp(12), 0);
- cell.addView(tileIcon(tile), iconSize);
+ cell.addView(tileIcon(tile, picked), iconSize);
LinearLayout texts = vertical();
TextView title = text(tile.title, 14, false);
title.setSingleLine(true);
@@ -6460,22 +6519,23 @@ private View wideTile(QsTile tile, boolean picked) {
TextView sub = text(tile.sub, 12, false);
sub.setSingleLine(true);
sub.setEllipsize(TextUtils.TruncateAt.END);
- sub.setAlpha(0.6f);
+ setDim(sub, 0.6f);
texts.addView(sub);
cell.addView(texts, new LinearLayout.LayoutParams(0, -2, 1f));
return cell;
}
/** The tile's icon, or its first letter when there's no icon to show. */
- private View tileIcon(QsTile tile) {
+ private View tileIcon(QsTile tile, boolean picked) {
if (tile.icon == null) {
TextView letter = text(tile.title.isEmpty() ? "?" : tile.title.substring(0, 1), 16, true);
letter.setGravity(Gravity.CENTER);
+ if (picked) letter.setTextColor(0xFFFFFFFF);
return letter;
}
Drawable d = tile.icon.getConstantState() != null
? tile.icon.getConstantState().newDrawable(getResources()).mutate() : tile.icon.mutate();
- if (tile.tint) d.setTint(0xFFFFFFFF);
+ if (tile.tint) d.setTint(picked ? 0xFFFFFFFF : (lightTheme ? 0xFF000000 : 0xFFFFFFFF));
ImageView view = new ImageView(this);
view.setImageDrawable(d);
return view;
@@ -6485,7 +6545,7 @@ private TextView tileArrow(String label, boolean enabled, Runnable onClick) {
TextView view = text(label, 22, false);
view.setGravity(Gravity.CENTER);
view.setEnabled(enabled);
- view.setAlpha(enabled ? 0.9f : 0.2f);
+ view.setAlpha(enabled ? (lightTheme ? 1f : 0.9f) : (lightTheme ? 0.6f : 0.2f));
view.setBackgroundResource(attrResource(android.R.attr.selectableItemBackgroundBorderless));
if (enabled) view.setOnClickListener(v -> onClick.run());
view.setLayoutParams(new LinearLayout.LayoutParams(dp(48), dp(40)));
@@ -6703,7 +6763,7 @@ private Drawable load(Context ui, String name, String pkg) {
private TextView note(String message) {
TextView view = text(message, 13, false);
- view.setAlpha(0.6f);
+ setDim(view, 0.6f);
view.setPadding(dp(16), dp(12), dp(16), dp(4));
return view;
}
@@ -6732,25 +6792,27 @@ private TextView chip(String icon, String title, String[] options, OnPage onSele
final int[] sel = {0};
TextView chip = text("", 13, true);
chip.setGravity(Gravity.CENTER);
- chip.setPadding(dp(16), dp(8), dp(16), dp(8));
+ chip.setSingleLine(true);
+ chip.setEllipsize(android.text.TextUtils.TruncateAt.END);
+ chip.setPadding(dp(12), dp(6), dp(12), dp(6));
chip.setTextColor(attrColor(android.R.attr.colorAccent));
GradientDrawable shape = new GradientDrawable();
shape.setCornerRadius(dp(22));
- shape.setColor(0xFF161616);
- shape.setStroke(dp(1), 0x40FFFFFF);
+ shape.setColor(lightTheme ? 0xFFFFFFFF : 0xFF161616);
+ shape.setStroke(dp(1), lightTheme ? 0x26000000 : 0x40FFFFFF);
chip.setBackground(shape);
LinearLayout.LayoutParams margin = new LinearLayout.LayoutParams(-2, -2);
margin.setMargins(0, 0, dp(8), 0);
chip.setLayoutParams(margin);
- Runnable render = () -> chip.setText(Tr.t(icon + title + ": " + options[sel[0]]));
+ Runnable render = () -> chip.setText((icon.isEmpty() ? "" : icon) + Tr.t(title) + ": " + Tr.t(options[sel[0]]));
chip.setOnClickListener(v -> {
String[] items = new String[options.length];
for (int i = 0; i < options.length; i++) {
- items[i] = (i == sel[0] ? "● " : "○ ") + options[i];
+ items[i] = (i == sel[0] ? "● " : "○ ") + Tr.t(options[i]);
}
new AlertDialog.Builder(this)
.setTitle(Tr.t(title))
- .setItems(Tr.all(items), (d, which) -> {
+ .setItems(items, (d, which) -> {
sel[0] = which;
render.run();
onSelect.run(which);
@@ -6831,7 +6893,7 @@ private View row(LinearLayout list, String title, String summary, View end, Runn
LinearLayout texts = vertical();
texts.addView(text(title, 16, false));
TextView sub = text(summary, 13, false);
- sub.setAlpha(0.7f);
+ setDim(sub, 0.7f);
texts.addView(sub);
row.addView(texts, new LinearLayout.LayoutParams(0, -2, 1f));
if (end != null) row.addView(end);
@@ -6854,7 +6916,7 @@ private View row(LinearLayout list, String title, String summary, View end, Runn
private TextView arrow() {
TextView view = text("›", 24, false);
- view.setAlpha(0.5f);
+ setDim(view, 0.5f);
view.setPadding(dp(12), 0, 0, 0);
return view;
}
@@ -6876,7 +6938,7 @@ private final class Row {
ip.setMargins(0, 0, dp(14), 0);
view.addView(icon, ip);
LinearLayout texts = vertical();
- second.setAlpha(0.7f);
+ setDim(second, 0.7f);
texts.addView(first);
texts.addView(second);
texts.addView(third);
@@ -6980,18 +7042,26 @@ private TextView text(String value, int sp, boolean bold) {
view.setText(Tr.t(value));
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp);
if (bold) view.setTypeface(Typeface.DEFAULT_BOLD);
+ if (lightTheme) view.setTextColor(0xFF000000);
return view;
}
+ private void setDim(TextView view, float darkAlpha) {
+ if (view == null) return;
+ view.setAlpha(lightTheme ? 1f : darkAlpha);
+ if (lightTheme) view.setTextColor(0xFF111111);
+ }
+
private Button button(String label, View.OnClickListener listener) {
Button button = new Button(this);
button.setText(Tr.t(label));
button.setAllCaps(false);
button.setOnClickListener(listener);
button.setStateListAnimator(null); // no raised shadow: a flat pill like the rest of the app
- button.setBackground(pillBackground(0xFF2C2C30));
+ button.setBackground(pillBackground(lightTheme ? 0xFFE0E0E4 : 0xFF2C2C30));
button.setTextColor(new android.content.res.ColorStateList(
- new int[][]{{-android.R.attr.state_enabled}, {}}, new int[]{0x66FFFFFF, 0xFFFFFFFF}));
+ new int[][]{{-android.R.attr.state_enabled}, {}},
+ new int[]{lightTheme ? 0x8A000000 : 0x66FFFFFF, lightTheme ? 0xFF000000 : 0xFFFFFFFF}));
return button;
}
@@ -6999,7 +7069,8 @@ private Button button(String label, View.OnClickListener listener) {
private Button primary(Button button) {
button.setBackground(pillBackground(attrColor(android.R.attr.colorAccent)));
button.setTextColor(new android.content.res.ColorStateList(
- new int[][]{{-android.R.attr.state_enabled}, {}}, new int[]{0x66FFFFFF, 0xFF0D0D0D}));
+ new int[][]{{-android.R.attr.state_enabled}, {}},
+ new int[]{lightTheme ? 0x8A000000 : 0x66FFFFFF, lightTheme ? 0xFFFFFFFF : 0xFF0D0D0D}));
return button;
}
@@ -7013,10 +7084,11 @@ private GradientDrawable roundRect(int color, int radiusDp) {
/** A fully rounded button background that ripples when pressed and goes dark when disabled. */
private android.graphics.drawable.Drawable pillBackground(int color) {
android.graphics.drawable.StateListDrawable states = new android.graphics.drawable.StateListDrawable();
- states.addState(new int[]{-android.R.attr.state_enabled}, roundRect(0xFF1E1E22, 24));
+ states.addState(new int[]{-android.R.attr.state_enabled}, roundRect(lightTheme ? 0xFFEBEBEF : 0xFF1E1E22, 24));
states.addState(new int[]{}, roundRect(color, 24));
return new android.graphics.drawable.RippleDrawable(
- android.content.res.ColorStateList.valueOf(0x33FFFFFF), states, roundRect(0xFF000000, 24));
+ android.content.res.ColorStateList.valueOf(lightTheme ? 0x20000000 : 0x33FFFFFF),
+ states, roundRect(lightTheme ? 0xFFFFFFFF : 0xFF000000, 24));
}
/** One line at the top of a tab: dimmed tools need Shizuku. Shown only while it isn't usable. */
@@ -7024,7 +7096,7 @@ private void shizukuNote(LinearLayout list) {
TextView note = text("Dimmed tools need Shizuku. Start it on the first tab.", 13, false);
note.setTextColor(attrColor(android.R.attr.colorAccent));
note.setPadding(dp(16), dp(12), dp(16), dp(12));
- note.setBackground(roundRect(0xFF121212, 16));
+ note.setBackground(roundRect(lightTheme ? 0xFFEBEBEF : 0xFF121212, 16));
note.setVisibility(Shell.ready() ? View.GONE : View.VISIBLE);
shizukuLabels.add(note);
LinearLayout.LayoutParams margin = new LinearLayout.LayoutParams(-1, -2);
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index d1e1198..2ad0c4f 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -10,4 +10,20 @@
- #000000
- #0D0D0D
+
+
+
diff --git a/docs/README.md b/docs/README.md
index 0fd1766..3b12474 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -45,4 +45,4 @@
-TinyKit 1.15.1 · Android 7.0+ · MIT
+TinyKit 1.17.2 · Android 7.0+ · MIT
diff --git a/docs/architecture.md b/docs/architecture.md
index 47fed3e..23d2725 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -207,8 +207,7 @@ saves a shell call each time.
five bottom tabs reset the stack. There are no fragments.
- **Lists** use one generic `Adapter` with a `Binder` lambda, and app icons are loaded
lazily.
-- **Theme**: `android:Theme.Material` with a pure black background (`#000000`), accent `#8C9EFF`,
- and green/blue/red for Real/Fake/Off.
+- **Theme**: Pure black (ndroid:Theme.Material, #000000) with accent #8C9EFF by default, or an optional soft light theme (ndroid:Theme.Material.Light, #F4F4F6) with accent #2962FF, and green/blue/red for Real/Fake/Off.
## Threading