Comparando con el nodejs-project de la APK oficial (la referencia buena para el movil, no el git del dev) salia que parte de lo que se saco de backend.js era codigo de movil del propio dev: apagar los modulos que no van, cortar rutas, retocar el HTML, las descargas de la copia de seguridad y ocultar tema y workflow en la bienvenida. - backend.js vuelve a ser su version de movil mas dos lineas (montar nuestro router); modules_view.js, su version de movil - mobile_layer.js se queda con lo nuestro: avatar, filtro y lanzador - la guia lista las 17 diferencias que quedan con la APK oficial (561 lineas) y explica que hay que comparar con la APK, no con el git
211 lines
10 KiB
Python
211 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Portada y esquemas del README, con la paleta del tema Acampada.
|
|
|
|
Las capturas de movil (01..08) se hacen aparte, con la app arrancada en un nodo de
|
|
pruebas a 412x915 y escala 2; este script compone 00-portada.png a partir de ellas y
|
|
dibuja esquema-interfaz.png, esquema-rama.png y esquema-llamadas.png.
|
|
El texto vive aqui: se cambia y se relanza, nunca se retocan los PNG a mano.
|
|
|
|
python3 docs/capturas/generar_portada.py
|
|
"""
|
|
import math
|
|
from pathlib import Path
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
ANTON = HERE.parents[1] / "src/client/assets/fonts/Anton-Regular.ttf"
|
|
SANS = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
|
|
|
|
BG, FG, NEON, MUTED = "#ffffff", "#000000", "#00e5ff", "#555555"
|
|
|
|
|
|
def font(path, size):
|
|
return ImageFont.truetype(str(path), size)
|
|
|
|
|
|
def phone(shot, width):
|
|
"""Una captura dentro de un marco negro grueso con la sombra cian de los botones."""
|
|
im = Image.open(HERE / shot).convert("RGB")
|
|
h = round(im.height * width / im.width)
|
|
im = im.resize((width, h), Image.LANCZOS)
|
|
pad, radius, shadow = 10, 34, 10
|
|
frame = Image.new("RGBA", (width + 2 * pad, h + 2 * pad + shadow), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(frame)
|
|
d.rounded_rectangle((0, shadow, width + 2 * pad - 1, h + 2 * pad + shadow - 1), radius, fill=NEON)
|
|
d.rounded_rectangle((0, 0, width + 2 * pad - 1, h + 2 * pad - 1), radius, fill=FG)
|
|
mask = Image.new("L", im.size, 0)
|
|
ImageDraw.Draw(mask).rounded_rectangle((0, 0, width - 1, h - 1), radius - pad, fill=255)
|
|
frame.paste(im, (pad, pad), mask)
|
|
return frame
|
|
|
|
|
|
def portada():
|
|
W, H = 1600, 1100
|
|
out = Image.new("RGB", (W, H), BG)
|
|
d = ImageDraw.Draw(out)
|
|
d.text((80, 40), "OASIS MOBILE", font=font(ANTON, 92), fill=FG)
|
|
bottom = d.textbbox((80, 40), "OASIS MOBILE", font=font(ANTON, 92))[3]
|
|
d.rectangle((80, bottom + 12, 420, bottom + 20), fill=FG)
|
|
d.text((80, 196), "Rama PRUEBAS del hacklab · base Oasis 1.3.3 · tema Acampada, pensado para el pulgar",
|
|
font=font(SANS, 27), fill=MUTED)
|
|
shots = [("01-portada.png", "Lanzador de módulos"), ("02-tribus.png", "Tribus en filas"),
|
|
("03-votacion.png", "Votar en semáforo"), ("07-oasismobile.png", "O el tema del dev")]
|
|
pw, gap, top = 320, 50, 270
|
|
x = (W - (4 * (pw + 20) + 3 * gap)) // 2
|
|
cap = font(ANTON, 34)
|
|
for shot, label in shots:
|
|
fr = phone(shot, pw)
|
|
out.paste(fr, (x, top), fr)
|
|
tw = d.textlength(label.upper(), font=cap)
|
|
d.text((x + (fr.width - tw) / 2, top + fr.height + 18), label.upper(), font=cap, fill=FG)
|
|
x += fr.width + gap
|
|
out.save(HERE / "00-portada.png", optimize=True)
|
|
|
|
|
|
def caja(d, box, titulo, lineas, relleno=BG):
|
|
x0, y0, x1, y1 = box
|
|
d.rounded_rectangle((x0, y0 + 6, x1, y1 + 6), 18, fill=FG)
|
|
d.rounded_rectangle(box, 18, fill=relleno, outline=FG, width=4)
|
|
t = font(ANTON, 30)
|
|
d.text(((x0 + x1 - d.textlength(titulo, font=t)) / 2, y0 + 18), titulo, font=t, fill=FG)
|
|
s = font(SANS, 18)
|
|
for i, l in enumerate(lineas):
|
|
d.text(((x0 + x1 - d.textlength(l, font=s)) / 2, y0 + 70 + i * 26), l, font=s, fill=FG)
|
|
|
|
|
|
def esquema():
|
|
W, H = 1400, 840
|
|
out = Image.new("RGB", (W, H), BG)
|
|
d = ImageDraw.Draw(out)
|
|
d.text((46, 26), "LA INTERFAZ DE MÓVIL, POR DENTRO", font=font(ANTON, 46), fill=FG)
|
|
bottom = d.textbbox((46, 26), "LA INTERFAZ DE MÓVIL, POR DENTRO", font=font(ANTON, 46))[3]
|
|
d.rectangle((46, bottom + 10, 260, bottom + 16), fill=FG)
|
|
d.text((46, bottom + 30), "Casi sesenta módulos no caben en una pantalla de teléfono. Se reparten en tres piezas.",
|
|
font=font(SANS, 18), fill=MUTED)
|
|
|
|
# el movil esquematico
|
|
px0, py0, px1, py1 = 110, 170, 470, 790
|
|
d.rounded_rectangle((px0, py0, px1, py1), 30, outline=FG, width=5)
|
|
d.rounded_rectangle((px0 + 16, py0 + 20, px1 - 16, py0 + 84), 14, fill=BG, outline=FG, width=3)
|
|
s = font(SANS, 15)
|
|
d.text((px0 + 46, py0 + 42), "logo · no molestar · avatar", font=s, fill=FG)
|
|
secciones = [("COMUNIDAD", 8), ("BIBLIOTECA", 2), ("GOBERNANZA", 2)]
|
|
y = py0 + 110
|
|
for nombre, n in secciones:
|
|
d.text((px0 + 40, y), nombre, font=font(ANTON, 17), fill=FG)
|
|
d.line((px0 + 40, y + 26, px1 - 40, y + 26), fill=FG, width=3)
|
|
y += 38
|
|
for i in range(n):
|
|
cx = px0 + 48 + (i % 4) * 76
|
|
if i and i % 4 == 0:
|
|
y += 62
|
|
d.rounded_rectangle((cx, y + 4, cx + 48, y + 52), 12, fill=FG)
|
|
d.rounded_rectangle((cx, y, cx + 48, y + 48), 12, fill=BG, outline=FG, width=3)
|
|
y += 72
|
|
d.rounded_rectangle((px0 + 16, py1 - 84, px1 - 16, py1 - 20), 14, fill=NEON, outline=FG, width=3)
|
|
d.text((px0 + 92, py1 - 62), "barra: 4 atajos que eliges tú", font=s, fill=FG)
|
|
|
|
bx0, bx1 = 560, 1320
|
|
cajas = [
|
|
((bx0, 190, bx1, 340), "ARRIBA — LA BARRA SUPERIOR",
|
|
["El logo vuelve al inicio y el avatar abre tu perfil.",
|
|
"En medio, el botón de No molestar del Teléfono.",
|
|
"Con muchos módulos activos salen también los filtros Personal y Social."], BG),
|
|
((bx0, 375, bx1, 565), "EN MEDIO — EL LANZADOR",
|
|
["Los módulos activos en una rejilla de iconos, por secciones,",
|
|
"como el cajón de apps del móvil. Los nombres son los del Oasis",
|
|
"oficial: Comunidad, Biblioteca, Gobernanza…",
|
|
"Solo en la pantalla de inicio; dentro de cada módulo, filtros en fichas."], BG),
|
|
((bx0, 600, bx1, 750), "ABAJO — LOS ATAJOS",
|
|
["Cuatro módulos a tu elección: ni uno más, que es para el pulgar.",
|
|
"Se cambian en Herramientas → Tu barra. La elección se guarda",
|
|
"en tu nodo, así que sigue ahí después de cerrar la aplicación."], NEON),
|
|
]
|
|
flechas = [((px1 + 10, py0 + 52), (bx0 - 12, 262)), ((px1 + 10, 440), (bx0 - 12, 470)),
|
|
((px1 + 10, py1 - 52), (bx0 - 12, 675))]
|
|
for (a, b) in flechas:
|
|
ang = math.atan2(b[1] - a[1], b[0] - a[0])
|
|
tip = lambda da, r: (b[0] - r * math.cos(ang + da), b[1] - r * math.sin(ang + da))
|
|
d.line((a, tip(0, 12)), fill=FG, width=4)
|
|
d.polygon([b, tip(0.45, 20), tip(-0.45, 20)], fill=FG)
|
|
for box, t, l, r in cajas:
|
|
caja(d, box, t, l, r)
|
|
out.save(HERE / "esquema-interfaz.png", optimize=True)
|
|
|
|
|
|
def flecha(d, a, b, ancho=4):
|
|
ang = math.atan2(b[1] - a[1], b[0] - a[0])
|
|
tip = lambda da, r: (b[0] - r * math.cos(ang + da), b[1] - r * math.sin(ang + da))
|
|
d.line((a, tip(0, 12)), fill=FG, width=ancho)
|
|
d.polygon([b, tip(0.45, 20), tip(-0.45, 20)], fill=FG)
|
|
|
|
|
|
def titulo(d, texto, sub):
|
|
d.text((46, 26), texto, font=font(ANTON, 46), fill=FG)
|
|
bottom = d.textbbox((46, 26), texto, font=font(ANTON, 46))[3]
|
|
d.rectangle((46, bottom + 10, 260, bottom + 16), fill=FG)
|
|
d.text((46, bottom + 30), sub, font=font(SANS, 18), fill=MUTED)
|
|
|
|
|
|
def esquema_rama():
|
|
"""Como va la rama encima de Oasis: lo del dev abajo, lo nuestro encima."""
|
|
W, H = 1400, 760
|
|
out = Image.new("RGB", (W, H), BG)
|
|
d = ImageDraw.Draw(out)
|
|
titulo(d, "CÓMO VA LA RAMA ENCIMA DE OASIS",
|
|
"Oasis lo cambia el dev, casi una versión al día. La rama solo cambia cómo se ve y se usa en el móvil.")
|
|
capa = [
|
|
((60, 210, 380, 400), "ASPECTO",
|
|
["mobile_base.css: la maqueta", "mobile_ux.css: márgenes, letra,", "botones y piezas nuevas",
|
|
"Acampada.css: el skin"], NEON),
|
|
((400, 210, 720, 400), "PANTALLAS",
|
|
["mobile_ui.js y las vistas", "mobile_*.js. En una vista", "del dev, como mucho una", "línea que las llama"], NEON),
|
|
((740, 210, 1060, 400), "RUTAS",
|
|
["fork_routes.js, delante", "del router del dev: lo", "que no atiende pasa al", "suyo"], NEON),
|
|
((1080, 210, 1340, 400), "ANDROID",
|
|
["la app del dev tal cual,", "con nuestro backend", "dentro y paquete", "propio"], NEON),
|
|
]
|
|
for box, t, l, r in capa:
|
|
caja(d, box, t, l, r)
|
|
d.text((60, 166), "LO DE LA RAMA", font=font(ANTON, 26), fill=FG)
|
|
for x in (220, 560, 900, 1210):
|
|
flecha(d, (x, 418), (x, 478))
|
|
caja(d, (60, 500, 1340, 690), "OASIS DEL DEV: NO SE TOCA",
|
|
["backend.js y su middleware · los modelos (lo que se publica y cómo se cuenta) · SSB y su arranque",
|
|
"las traducciones · style.css y mobile.css · los temas, también OasisMobile.css",
|
|
"Comparado con su APK oficial, la rama cambia 17 ficheros suyos y 561 líneas. Ver GUIA_DE_DESARROLLO.md"], BG)
|
|
out.save(HERE / "esquema-rama.png", optimize=True)
|
|
|
|
|
|
def esquema_llamadas():
|
|
"""Las llamadas son las del dev; en Android pasan por un puente nativo de la app."""
|
|
W, H = 1400, 700
|
|
out = Image.new("RGB", (W, H), BG)
|
|
d = ImageDraw.Draw(out)
|
|
titulo(d, "LLAMADAS: LAS DEL DEV",
|
|
"Teléfono y Salas son de Oasis. En Android el sonido y la cámara pasan por un puente nativo de la app.")
|
|
cajas = [
|
|
((50, 200, 360, 430), "TELÉFONO Y SALAS",
|
|
["De Oasis, tal cual.", "Llamadas de uno a uno", "con buzón de voz; salas", "de hasta 50, con vídeo,", "grabación y emisión."], BG),
|
|
((400, 200, 700, 430), "LOS ENCHUFES",
|
|
["phone_*_android.js", "(del dev): se conectan", "a un puerto local que", "deja la app en", ".audio_bridge"], BG),
|
|
((740, 200, 1040, 430), "EL PUENTE NATIVO",
|
|
["Dentro de la app:", "R graba el micro", "P reproduce", "V saca la cámara", "S emite un fichero"], NEON),
|
|
((1080, 200, 1350, 430), "EL MÓVIL",
|
|
["Micrófono, cámara", "y altavoz, con los", "permisos que se piden", "la primera vez."], BG),
|
|
]
|
|
for box, t, l, r in cajas:
|
|
caja(d, box, t, l, r)
|
|
for a, b in (((362, 315), (398, 315)), ((702, 315), (738, 315)), ((1042, 315), (1078, 315))):
|
|
flecha(d, a, b)
|
|
caja(d, (50, 480, 1350, 630), "DÓNDE ESTÁ CADA COSA",
|
|
["El puente está en la app Android del dev, que no tiene el código publicado. Por eso la APK de la",
|
|
"rama se monta sobre la oficial: su app tal cual, nuestro backend dentro y paquete propio."], BG)
|
|
out.save(HERE / "esquema-llamadas.png", optimize=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
portada()
|
|
esquema()
|
|
esquema_rama()
|
|
esquema_llamadas()
|