Private
Public Access
Minamp 1.6: adjustable UI size, larger text and controls
- New setting "Text and button size" (Normal / Large / Extra large): scales text, icons and buttons on top of the phone's density (App.UI_SCALES) - Larger default text, rows, tiles, touch targets and player icons; all sizes now live in res/values/dimens.xml - Seek bar knob drawn in code, since Android 2.x caches XML shapes at the size they were first loaded - Full-width buttons wrap to two lines instead of cutting words off - Shorter labels so they fit 240x320 screens in both languages - tools/check_fit.py checks that UI texts fit their slots at every size and language using Droid Sans metrics; build.sh runs it and setup_toolchain.sh fetches the font Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fa67e95fde
commit
bbdab09f83
32 files changed
+457
-190
No files matched your search
@@ -0,0 +1,118 @@
|
||||
"""Checks that UI texts fit their slots on a 240x320 ldpi screen, in every language and UI size.
|
||||
|
||||
Uses Droid Sans (Android 2.x's system font) metrics. A slot's width is computed from the screen
|
||||
width in dp, which shrinks as the UI scale grows (240 px / (0.75 * scale)). Texts that are
|
||||
expected to ellipsize (album titles etc.) aren't checked; fixed UI words are.
|
||||
Run: python3 tools/check_fit.py [font_dir]
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PIL import ImageFont
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from strings import S # noqa: E402
|
||||
|
||||
FONT_DIR = sys.argv[1] if len(sys.argv) > 1 else "/home/claude/tools/fonts/x/connect-fonts-droidsans/package/fonts/latin"
|
||||
REG = ImageFont.truetype(os.path.join(FONT_DIR, "droidsans-regular.ttf"), 200)
|
||||
BOLD = ImageFont.truetype(os.path.join(FONT_DIR, "droidsans-bold.ttf"), 200)
|
||||
SCALES = {"Normal": 1.0, "Grande": 1.15, "Moi grande": 1.3} # keep in sync with App.UI_SCALES
|
||||
SCREEN_PX = 240
|
||||
LDPI = 0.75
|
||||
SAFETY = 1.04 # hinting / rounding headroom
|
||||
|
||||
|
||||
def width_dp(text, sp, bold):
|
||||
f = BOLD if bold else REG
|
||||
return f.getlength(text) * sp / 200.0 * SAFETY
|
||||
|
||||
|
||||
def lines_needed(text, sp, bold, avail):
|
||||
words = text.replace("\n", " \n ").split(" ")
|
||||
lines, cur = 1, ""
|
||||
for w in words:
|
||||
if w == "\n":
|
||||
lines += 1
|
||||
cur = ""
|
||||
continue
|
||||
trial = (cur + " " + w).strip()
|
||||
if width_dp(trial, sp, bold) <= avail:
|
||||
cur = trial
|
||||
else:
|
||||
if cur:
|
||||
lines += 1
|
||||
cur = w
|
||||
if width_dp(w, sp, bold) > avail:
|
||||
return 99 # a single word doesn't fit
|
||||
return lines
|
||||
|
||||
|
||||
def texts(keys, fmt_args=()):
|
||||
out = []
|
||||
for k in keys:
|
||||
en, gl = S[k]
|
||||
for lang, t in (("en", en), ("gl", gl)):
|
||||
if fmt_args:
|
||||
try:
|
||||
t = t.replace("%1$s", fmt_args[0]).replace("%1$d", fmt_args[0]).replace("%2$d", fmt_args[1] if len(fmt_args) > 1 else "")
|
||||
except Exception:
|
||||
pass
|
||||
out.append((k, lang, t))
|
||||
return out
|
||||
|
||||
|
||||
# slot: (name, keys, sp, bold, width_fn(screen_dp), max_lines, fmt_args)
|
||||
def slots(D):
|
||||
sw = lambda W: W # noqa: E731
|
||||
return [
|
||||
("tab", ["tab_home", "tab_artists", "tab_albums", "tab_playlists"], D["text_tab"], True,
|
||||
lambda W: W / 4 - 4, 1, ()),
|
||||
("detail button", ["play", "shuffle", "play_all", "artist_radio"], D["text_button"], True,
|
||||
lambda W: (W - 2 * 10 - 10) / 2 - 2 * 4, 1, ()),
|
||||
("home tile title", ["library_radio", "loved_radio", "time_travel", "random_album"], D["text_tile"], True,
|
||||
lambda W: W - 2 * 8 - 12 - 10 - D["tile_icon"] - 12, 1, ()),
|
||||
("home tile sub", ["library_radio_sub", "loved_radio_sub", "time_travel_sub", "random_album_sub"], D["text_small"], False,
|
||||
lambda W: W - 2 * 8 - 12 - 10 - D["tile_icon"] - 12, 1, ()),
|
||||
("shelf header + see all", ["recently_added", "recently_played"], D["text_small"], True,
|
||||
lambda W: W - 10 - width_dp(S["see_all"][1], D["text_small"], True) - 20, 1, ()),
|
||||
("now playing: playing from", ["playing_from"], D["text_tiny"], True,
|
||||
lambda W: W - 2 * D["touch"] - 8, 1, ()),
|
||||
("now playing: sleep", ["sleep", "end_of_track"], D["text_np_small"], False,
|
||||
lambda W: W - 16 - 16 - D["np_small_icon"] - 6 - width_dp(S["album_link"][1], D["text_np_small"], False) - 16, 1, ()),
|
||||
("now playing: album link", ["album_link"], D["text_np_small"], False,
|
||||
lambda W: (W - 24) / 2 - 16, 1, ()),
|
||||
("setup button", ["sign_in", "connect_address", "pin_check_now", "cancel", "connect", "back"], D["text_button"], True,
|
||||
lambda W: W - 2 * 20 - 2 * D["button_pad"], 1, ()),
|
||||
("settings: size options", ["size_normal", "size_large", "size_xlarge"], 18, False, lambda W: W - 80, 1, ()),
|
||||
("setup tagline", ["tagline"], D["text_sub"], False, lambda W: W - 40, 3, ()),
|
||||
("pin status", ["pin_not_yet"], D["text_sub"], False, lambda W: W - 40 - 24, 3, ("12", "11")),
|
||||
("pin instructions", ["pin_open"], D["text_sub"], False, lambda W: W - 40, 2, ()),
|
||||
("queue title", ["up_next"], D["text_title"], True, lambda W: W - 3 * D["touch"] - 12, 1, ()),
|
||||
("search hint", ["search_hint"], D["text_body"], False, lambda W: W - D["touch"] - 12 - 20, 1, ()),
|
||||
("status message", ["queue_empty", "no_playlists", "pick_something", "nothing_playing"], D["text_status"], False,
|
||||
lambda W: W - 48, 2, ()),
|
||||
]
|
||||
|
||||
|
||||
def run(D):
|
||||
bad = 0
|
||||
for scale_name, s in SCALES.items():
|
||||
W = SCREEN_PX / (LDPI * s)
|
||||
for name, keys, sp, bold, wfn, max_lines, args in slots(D):
|
||||
avail = wfn(W)
|
||||
for k, lang, t in texts(keys, args):
|
||||
n = lines_needed(t, sp, bold, avail)
|
||||
if n > max_lines:
|
||||
bad += 1
|
||||
print("DOESN'T FIT %-11s %-26s %s %-24r needs %s line(s) in %.0fdp (max %d)"
|
||||
% (scale_name, name, lang, t, "word too long:" if n == 99 else n, avail, max_lines))
|
||||
return bad
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import re
|
||||
dimens = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "res", "values", "dimens.xml")).read()
|
||||
D = {m.group(1): float(m.group(2)) for m in re.finditer(r'<dimen name="(\w+)">([\d.]+)(?:sp|dp)</dimen>', dimens)}
|
||||
n = run(D)
|
||||
print("all texts fit" if n == 0 else "%d text(s) don't fit" % n)
|
||||
sys.exit(1 if n else 0)
|
||||
@@ -54,6 +54,14 @@ PY
|
||||
python3 "$PROJ/tools/patch_bc_dalvik.py" bcsrc
|
||||
fi
|
||||
|
||||
echo "== Droid Sans (Android 2.x's font), for tools/check_fit.py"
|
||||
if [ ! -f fonts/droidsans-regular.ttf ]; then
|
||||
mkdir -p fonts dl/fonts
|
||||
curl -sSL -o dl/droid.tgz "$(curl -sS https://registry.npmjs.org/connect-fonts-droidsans/latest | python3 -c 'import json,sys;print(json.load(sys.stdin)["dist"]["tarball"])')"
|
||||
tar xzf dl/droid.tgz -C dl/fonts
|
||||
cp dl/fonts/package/fonts/latin/droidsans-regular.ttf dl/fonts/package/fonts/latin/droidsans-bold.ttf fonts/
|
||||
fi
|
||||
|
||||
echo "== signing key"
|
||||
if [ ! -f minamp.keystore ]; then
|
||||
if [ -f "$PROJ/signing/minamp.keystore" ]; then cp "$PROJ/signing/minamp.keystore" .; else
|
||||
|
||||
+19
-14
@@ -10,10 +10,10 @@ S = {
|
||||
"app_name": ("Minamp", "Minamp"),
|
||||
|
||||
# tabs / headers
|
||||
"tab_home": ("HOME", "INICIO"),
|
||||
"tab_artists": ("ARTISTS", "ARTISTAS"),
|
||||
"tab_albums": ("ALBUMS", "ÁLBUMS"),
|
||||
"tab_playlists": ("PLAYLISTS", "LISTAS"),
|
||||
"tab_home": ("Home", "Inicio"),
|
||||
"tab_artists": ("Artists", "Artistas"),
|
||||
"tab_albums": ("Albums", "Álbums"),
|
||||
"tab_playlists": ("Playlists", "Listas"),
|
||||
"playing_from": ("PLAYING FROM", "REPRODUCINDO DESDE"),
|
||||
"see_all": ("SEE ALL ›", "VER TODO ›"),
|
||||
"album_link": ("Album ›", "Álbum ›"),
|
||||
@@ -21,12 +21,12 @@ S = {
|
||||
# setup
|
||||
"tagline": ("Your Plex music, anywhere,\non a very small phone.", "A túa música de Plex, onde queiras,\nnun teléfono moi pequeno."),
|
||||
"sign_in": ("Sign in with Plex", "Iniciar sesión con Plex"),
|
||||
"connect_address": ("Connect to a server address", "Conectar a un enderezo de servidor"),
|
||||
"connect_address": ("Connect by address", "Conectar por enderezo"),
|
||||
"connection_log": ("Connection log", "Rexistro de conexión"),
|
||||
"pin_open": ("On a computer or a newer phone, open", "Nun ordenador ou nun teléfono máis novo, abre"),
|
||||
"pin_enter": ("and enter this code:", "e escribe este código:"),
|
||||
"pin_waiting": ("Waiting for you to link…", "Agardando a que vincules a conta…"),
|
||||
"pin_check_now": ("I've entered it — check now", "Xa o escribín: comprobar agora"),
|
||||
"pin_check_now": ("Check now", "Comprobar agora"),
|
||||
"cancel": ("Cancel", "Cancelar"),
|
||||
"server_address": ("Server address", "Enderezo do servidor"),
|
||||
"server_address_help": ("e.g. 192.168.1.20 or https://plex.example.com. Signing in with Plex is easier and finds remote addresses for you.",
|
||||
@@ -90,9 +90,9 @@ S = {
|
||||
|
||||
# actions
|
||||
"play": ("Play", "Reproducir"),
|
||||
"play_all": ("Play all", "Reproducir todo"),
|
||||
"play_all": ("Play all", "Reproducir"),
|
||||
"shuffle": ("Shuffle", "Aleatorio"),
|
||||
"artist_radio": ("Artist radio", "Radio do artista"),
|
||||
"artist_radio": ("Radio", "Radio"),
|
||||
"artist_radio_shuffle": ("Artist radio (shuffle)", "Radio do artista (aleatorio)"),
|
||||
"play_next": ("Play next", "Reproducir a continuación"),
|
||||
"add_to_queue": ("Add to queue", "Engadir á cola"),
|
||||
@@ -129,6 +129,11 @@ S = {
|
||||
"set_connection": ("Connection: %1$s", "Conexión: %1$s"),
|
||||
"set_log": ("Connection log & streaming test", "Rexistro de conexión e proba de reprodución"),
|
||||
"set_language": ("Language: %1$s", "Idioma: %1$s"),
|
||||
"set_size": ("Text and button size: %1$s", "Tamaño de texto e botóns: %1$s"),
|
||||
"size_title": ("Text and button size", "Tamaño de texto e botóns"),
|
||||
"size_normal": ("Normal", "Normal"),
|
||||
"size_large": ("Large", "Grande"),
|
||||
"size_xlarge": ("Extra large", "Moi grande"),
|
||||
"sign_out": ("Sign out", "Pechar sesión"),
|
||||
"quality_home_title": ("Quality at home", "Calidade na casa"),
|
||||
"quality_away_title": ("Quality away / mobile", "Calidade fóra / datos móbiles"),
|
||||
@@ -158,11 +163,11 @@ S = {
|
||||
"loved_radio": ("Favourites radio", "Radio de favoritas"),
|
||||
"loved_radio_sub": ("Your top rated", "As mellor valoradas"),
|
||||
"time_travel": ("Time travel", "Viaxe no tempo"),
|
||||
"time_travel_sub": ("Pick a decade", "Escolle unha década"),
|
||||
"time_travel_sub": ("Pick a decade", "Escolle década"),
|
||||
"random_album": ("Random album", "Álbum ao chou"),
|
||||
"random_album_sub": ("Feeling lucky", "Proba sorte"),
|
||||
"recently_added": ("Recently added", "Engadidos recentemente"),
|
||||
"recently_played": ("Recently played", "Escoitados recentemente"),
|
||||
"recently_added": ("Recently added", "Novidades"),
|
||||
"recently_played": ("Recently played", "Últimas escoitas"),
|
||||
"finding_decades": ("Finding decades…", "Buscando décadas…"),
|
||||
"no_years": ("No release years in this library", "Esta biblioteca non ten anos de publicación"),
|
||||
"surprise_me": ("Surprise me", "Sorpréndeme"),
|
||||
@@ -192,7 +197,7 @@ S = {
|
||||
"nothing_playing": ("Nothing playing", "Non se está a reproducir nada"),
|
||||
"pick_something": ("Pick something from your library", "Escolle algo da túa biblioteca"),
|
||||
"end_of_track": ("End of track", "Fin da pista"),
|
||||
"sleep": ("Sleep", "Temporizador"),
|
||||
"sleep": ("Sleep", "Durmir"),
|
||||
"sleep_timer": ("Sleep timer", "Temporizador de apagado"),
|
||||
"sleep_off": ("Off", "Desactivado"),
|
||||
"sleep_15": ("15 minutes", "15 minutos"),
|
||||
@@ -211,7 +216,7 @@ S = {
|
||||
"paused_dot": ("Paused · ", "En pausa · "),
|
||||
|
||||
# queue
|
||||
"up_next": ("Up next", "A continuación"),
|
||||
"up_next": ("Queue", "Cola"),
|
||||
"shuffle_on": ("Shuffle on", "Aleatorio activado"),
|
||||
"shuffle_off": ("Shuffle off", "Aleatorio desactivado"),
|
||||
"remove_from_queue": ("Remove from queue", "Quitar da cola"),
|
||||
@@ -220,7 +225,7 @@ S = {
|
||||
"clear_everything": ("Clear everything", "Borralo todo"),
|
||||
|
||||
# search
|
||||
"search_hint": ("Artists, albums, tracks", "Artistas, álbums, cancións"),
|
||||
"search_hint": ("Search music", "Buscar música"),
|
||||
"search_in": ("Search %1$s", "Buscar en %1$s"),
|
||||
"searching": ("Searching…", "Buscando…"),
|
||||
"nothing_found_q": ("Nothing found for “%1$s”", "Non se atopou nada para “%1$s”"),
|
||||
|
||||
Reference in new issue
Block a user