Estadísticas de lectura en el perfil, y un perfil que el usuario puede ordenar #18

Closed
HBB wants to merge 0 commits from HBB/rocinante_android:feat/profile-stats into main
First-time contributor

Esta rama convierte el perfil en un resumen de la propia lectura. Nada de esto añade
peticiones a la instancia
: todo se calcula con lo que la aplicación ya tiene en caché
—la estantería «Leídos» y los datos enriquecidos— así que abrir el perfil cuesta lo mismo
que antes. Me pareció importante teniendo en cuenta Anubis.

Qué se ve

  • Cifras y libros por año. Bajo los seguidores: libros leídos, libros de este año y
    páginas acumuladas, con un gráfico de barras de libros por año dibujado con Canvas (sin
    añadir ninguna dependencia de gráficos).
  • Reto de lectura. Dice si se va por delante o por detrás del ritmo que exige la meta
    hoy, y cuántos días se tarda en leer un libro, este año y en total.
  • Por leer. Fila horizontal de portadas, junto a la de «Leyendo actualmente».
  • Autores más leídos, reparto de valoraciones, idiomas (con su bandera) y
    formatos, en barras horizontales.
  • «Editar página», al final: un diálogo donde activar o desactivar cada bloque y
    arrastrarlos para cambiar su orden. La cabecera —avatar, nombre y seguidores— queda
    fuera a propósito: es la identidad del perfil.

Decisiones que conviene conocer al revisar

Las cifras dicen sobre qué están calculadas. En una estantería real, BookWyrm no trae
todos los datos: de mis 37 libros leídos, solo 24 traen páginas y únicamente 5 tienen fecha
de inicio y de fin. Cada tarjeta indica cuántos libros quedan fuera en lugar de presentar
un total que parecería completo y no lo sería.

Los autores llegan en un solo texto separado por comas, y esa coma es ambigua. En datos
reales conviven «Linus Torvalds, David Diamond» (dos personas) y «Henry, Ford» (una sola,
con el apellido primero). Solo se separa cuando todas las partes parecen un nombre completo;
agrupar de más nunca inventa a alguien que no existe, separar de más sí.

Los idiomas se agrupan por bandera y no por su texto, porque una misma estantería mezcla
«Danish» y «Dansk» y por texto se contarían como dos idiomas distintos.

El formato ya venía en el .json de la estantería (physicalFormat, en el mismo Edition
que las páginas y los idiomas) y se estaba descartando; basta con recogerlo.

La disposición del perfil está preparada para el futuro. Se guarda como los ids separados
por comas; al leerla, un id desconocido se ignora y los bloques que la versión conoce pero no
estaban guardados se añaden al final, visibles. Sin eso, un bloque nuevo no aparecería a quien
ya tuviera su propia disposición. Hay una prueba que falla si alguien renombra un id, porque
invalidaría las disposiciones ya guardadas en los dispositivos.

Un error aparte, en su propio commit

fix: la caché de la estantería guardaba solo la primera página no tiene que ver con las
estadísticas y se puede revisar por separado: la estantería se recorre entera pero en la
caché solo se escribía la primera página, así que sin conexión se veía truncada a diez libros.

Comprobado

  • 28 pruebas unitarias nuevas y existentes en verde. La lógica vive en funciones puras
    (ReadingStats, ReadingGoalPace, ProfileLayout), y el ritmo del reto recibe la fecha
    como parámetro para poder comprobarse.
  • Probado en un dispositivo real contra bookwyrm.social.
  • Los 17 idiomas están al día: las 29 cadenas y los 9 plurales nuevos están traducidos a
    todos, con las reglas de plural propias de cada lengua (one/few/many en polaco y ucraniano,
    one/few/other en checo y rumano) y comprobando que ningún idioma pierde un especificador de
    formato. Aviso honesto: no las ha revisado un hablante nativo de cada idioma, así que alguna
    expresión puede mejorarse, sobre todo en las lenguas eslavas y en griego, finés y rumano.

Detalle a tener en cuenta al probarlo

Las tarjetas no aparecen hasta que se ha abierto la estantería «Leídos» al menos una vez, que
es cuando se llenan esos datos. Y el reparto de formatos necesita además una carga de la
estantería con esta versión, porque las estanterías ya cacheadas no guardaban ese campo.

Esta rama convierte el perfil en un resumen de la propia lectura. **Nada de esto añade peticiones a la instancia**: todo se calcula con lo que la aplicación ya tiene en caché —la estantería «Leídos» y los datos enriquecidos— así que abrir el perfil cuesta lo mismo que antes. Me pareció importante teniendo en cuenta Anubis. ## Qué se ve - **Cifras y libros por año.** Bajo los seguidores: libros leídos, libros de este año y páginas acumuladas, con un gráfico de barras de libros por año dibujado con Canvas (sin añadir ninguna dependencia de gráficos). - **Reto de lectura.** Dice si se va por delante o por detrás del ritmo que exige la meta hoy, y cuántos días se tarda en leer un libro, este año y en total. - **Por leer.** Fila horizontal de portadas, junto a la de «Leyendo actualmente». - **Autores más leídos**, **reparto de valoraciones**, **idiomas** (con su bandera) y **formatos**, en barras horizontales. - **«Editar página»**, al final: un diálogo donde activar o desactivar cada bloque y arrastrarlos para cambiar su orden. La cabecera —avatar, nombre y seguidores— queda fuera a propósito: es la identidad del perfil. ## Decisiones que conviene conocer al revisar **Las cifras dicen sobre qué están calculadas.** En una estantería real, BookWyrm no trae todos los datos: de mis 37 libros leídos, solo 24 traen páginas y únicamente 5 tienen fecha de inicio *y* de fin. Cada tarjeta indica cuántos libros quedan fuera en lugar de presentar un total que parecería completo y no lo sería. **Los autores llegan en un solo texto separado por comas, y esa coma es ambigua.** En datos reales conviven «Linus Torvalds, David Diamond» (dos personas) y «Henry, Ford» (una sola, con el apellido primero). Solo se separa cuando todas las partes parecen un nombre completo; agrupar de más nunca inventa a alguien que no existe, separar de más sí. **Los idiomas se agrupan por bandera y no por su texto**, porque una misma estantería mezcla «Danish» y «Dansk» y por texto se contarían como dos idiomas distintos. **El formato ya venía en el .json** de la estantería (`physicalFormat`, en el mismo Edition que las páginas y los idiomas) y se estaba descartando; basta con recogerlo. **La disposición del perfil está preparada para el futuro.** Se guarda como los ids separados por comas; al leerla, un id desconocido se ignora y los bloques que la versión conoce pero no estaban guardados se añaden al final, visibles. Sin eso, un bloque nuevo no aparecería a quien ya tuviera su propia disposición. Hay una prueba que falla si alguien renombra un id, porque invalidaría las disposiciones ya guardadas en los dispositivos. ## Un error aparte, en su propio commit `fix: la caché de la estantería guardaba solo la primera página` no tiene que ver con las estadísticas y se puede revisar por separado: la estantería se recorre entera pero en la caché solo se escribía la primera página, así que sin conexión se veía truncada a diez libros. ## Comprobado - 28 pruebas unitarias nuevas y existentes en verde. La lógica vive en funciones puras (`ReadingStats`, `ReadingGoalPace`, `ProfileLayout`), y el ritmo del reto recibe la fecha como parámetro para poder comprobarse. - Probado en un dispositivo real contra bookwyrm.social. - **Los 17 idiomas están al día**: las 29 cadenas y los 9 plurales nuevos están traducidos a todos, con las reglas de plural propias de cada lengua (one/few/many en polaco y ucraniano, one/few/other en checo y rumano) y comprobando que ningún idioma pierde un especificador de formato. Aviso honesto: no las ha revisado un hablante nativo de cada idioma, así que alguna expresión puede mejorarse, sobre todo en las lenguas eslavas y en griego, finés y rumano. ## Detalle a tener en cuenta al probarlo Las tarjetas no aparecen hasta que se ha abierto la estantería «Leídos» al menos una vez, que es cuando se llenan esos datos. Y el reparto de formatos necesita además una carga de la estantería con esta versión, porque las estanterías ya cacheadas no guardaban ese campo.
HBB added 10 commits 2026-07-25 11:56:37 +00:00
Aunque la estantería se recorre entera (todas las páginas del .json), en la caché
solo se escribía la primera. La lista quedaba truncada a diez libros hasta la
siguiente carga completa, de modo que sin conexión se veía una estantería
incompleta y cualquier cálculo hecho sobre la caché contaba de menos.

Ahora la primera página solo se guarda cuando todavía no hay nada cacheado, para
no truncar una lista completa mientras se pagina, y al terminar de recorrer todas
las páginas se persiste la lista entera.
Bajo los seguidores se añade una tarjeta con tres cifras —libros leídos, libros de
este año y páginas acumuladas— y un gráfico de barras de libros por año.

Todo se calcula con datos que ya están en el dispositivo (la estantería «Leídos» y
la caché de enriquecimiento con las fechas de fin en ISO), así que abrir el perfil
no dispara ninguna petición nueva. La tarjeta no aparece hasta que la estantería se
ha abierto al menos una vez, que es cuando se rellenan esos datos.

Decisiones del gráfico:

- Los años sin lecturas ocupan su hueco, para que el eje no comprima el tiempo.
- Solo se etiquetan el año en curso y el de más lecturas, en vez de repetir una
  cifra sobre cada barra. El año en curso va a color pleno y el resto atenuado.
- Con un único año no se dibuja gráfico: dos puntos no son una serie temporal.
- Una sola serie, sin leyenda; los colores salen del tema Material 3, de modo que
  el modo claro y el oscuro se resuelven solos.
- El gráfico expone su contenido como texto para los lectores de pantalla.

Los datos que faltan se dicen en la propia tarjeta: los libros sin fecha de fin no
entran en el gráfico y los que no indican páginas no suman en el total, y ambos
casos se advierten. Sin ese aviso, las cifras aparentarían ser totales completos.

El cálculo vive en `utils/ReadingStats.kt` como función pura, con pruebas unitarias
del relleno de años, las fechas imposibles y los datos ausentes.

Cadenas nuevas en inglés, español y danés; el resto de idiomas recurre al inglés
hasta que se apruebe la interfaz.
Bajo «Leyendo actualmente» se añade una fila horizontal con las portadas de la
estantería «Por leer». Solo portada: el resto de los datos está a un toque, en la
ficha del libro, que se abre igual que desde la fila de lectura actual.

Las dos filas comparten ahora un único componente, `ProfileBookCoverRow`. Antes el
bloque de «Leyendo actualmente» repetía el mismo manejador de pulsación dos veces
—una para los libros con portada y otra para los que no la tienen—, así que reunirlo
en un solo `openBook` elimina más líneas de las que añade esta pantalla.

Se reutiliza la cadena `shelf_to_read_title`, ya traducida a los 17 idiomas, de modo
que la fila tiene título correcto en todos ellos sin añadir cadenas nuevas.

De la estantería solo se pide la primera página, que es cuanto necesita una fila de
portadas, y únicamente se siembra la caché cuando aún no hay nada guardado: la
pantalla de estanterías guarda la lista completa y el perfil no debe truncarla.

Los libros sin portada muestran un marcador con icono en lugar de un hueco, y las
portadas anuncian el título del libro a los lectores de pantalla.
Bajo el reto de lectura se añade una tarjeta con los diez autores más leídos, en
barras horizontales porque los nombres son textos largos que en vertical no caben.
Cada fila lleva su cifra al final: aquí el número es el contenido, no un adorno.
Como el resto de la tarjeta de estadísticas, sale de la caché y no cuesta ninguna
petición.

La parte delicada es que BookWyrm entrega los autores de un libro en un solo texto
separado por comas, y esa coma es ambigua. En datos reales conviven los dos casos:
«Linus Torvalds, David Diamond» son dos personas y «Henry, Ford» es una sola escrita
con el apellido primero. Separar por la coma sin más inventaría dos autores
inexistentes, «Henry» y «Ford». Por eso solo se separa cuando todas las partes
parecen un nombre completo —llevan un espacio dentro— y en cualquier otro caso se
cuenta como un autor único: agrupar de más nunca crea a alguien que no existe,
mientras que separar de más sí. La regla tiene sus propias pruebas unitarias con
esos dos ejemplos.

La tarjeta no aparece con menos de dos autores, porque entonces no compara nada, y
los libros de los que no se conoce el autor se indican en lugar de desaparecer sin
más de la cuenta.

Cadenas nuevas en inglés, español y danés.
Las estrellas de una valoración vivían como función privada dentro de
MyBooksScreen, de modo que ninguna otra pantalla podía dibujar una valoración sin
reimplementarla —y media estrella acabaría representándose de dos maneras
distintas—. Se mueve a ui/components/RatingStars.kt con el tamaño configurable y
sin cambiar su aspecto.

De paso se sustituye Icons.Filled.StarHalf, que está obsoleto y no se refleja en
los idiomas que se escriben de derecha a izquierda, por su variante AutoMirrored;
con ello desaparece también el aviso de compilación que arrastraba.
Bajo los autores más leídos se añade una tarjeta con la media de las valoraciones
propias y cuántos libros ha recibido cada nota. Comparte anatomía con el gráfico de
autores —etiqueta a la izquierda, barra en medio y cifra al final— y ambas usan ya
la misma fila, de forma que no puedan separarse visualmente con el tiempo. La
etiqueta es un hueco libre: aquí lleva las estrellas de `RatingStars`, las mismas
que muestran las estanterías, en lugar de un texto propio.

Las cinco estrellas enteras aparecen siempre, incluso a cero, para que se vea la
forma del reparto; las medias solo si de verdad se han usado, porque con datos
reales la mayoría no se usan nunca y llenarían el gráfico de filas vacías.

La media se calcula solo sobre los libros valorados: los que no lo están no tiran de
ella hacia abajo y se indican aparte, igual que se hace con las páginas y las fechas
que faltan.

Cadenas nuevas en inglés, español y danés.
La tarjeta del reto anual pasa a decir si se va al día: reparte la meta de forma
uniforme entre los días del año y la compara con la fecha de hoy, para indicar
cuántos libros de adelanto o de retraso se llevan. La fecha entra como parámetro en
lugar de leer el reloj dentro del cálculo, de modo que el ritmo se puede comprobar
con pruebas; los años bisiestos reparten la meta entre 366 días y también tienen la
suya.

Debajo, con la misma disposición que seguidores y seguidos, se añaden los días que
se tarda en leer un libro: este año y en total.

Ese dato exige fecha de inicio Y de fin, y BookWyrm rara vez guarda la de inicio: en
una estantería real solo cinco de cuarenta libros la tienen. Por eso la tarjeta
escribe sobre cuántos libros está calculada la media en lugar de aparentar que
resume la estantería entera, y si no hay ninguno con las dos fechas las cifras
desaparecen y queda solo el ritmo. Los tramos con las fechas invertidas se descartan
para que no tiren de la media, con su prueba correspondiente.

La tarjeta del reto adopta además el contorno de las tarjetas de estadísticas:
muestra cifras del mismo tipo y debe leerse como parte del mismo grupo.

Cadenas nuevas en inglés, español y danés.
Dos tarjetas más, con las mismas barras horizontales que autores y valoraciones: los
idiomas en que se lee, con su bandera delante del nombre, y el formato de los
ejemplares. Ninguna cuesta una petición: los dos datos ya viajaban en el Edition del
.json de la estantería.

El formato (physicalFormat) se descartaba al leer el .json, igual que ocurría antes
con las páginas y los idiomas; basta con recogerlo en ShelfBookItem. Los valores de
BookWyrm —Hardcover, Paperback, EBook, AudiobookFormat, GraphicNovel— se traducen en
la interfaz, y uno desconocido se muestra tal cual en vez de ocultarse. Las
estanterías ya cacheadas no lo traen, así que la tarjeta no aparece hasta la
siguiente carga de la estantería.

Los idiomas se agrupan por BANDERA y no por el texto: BookWyrm guarda el idioma tal
como venga en la edición, de modo que una misma estantería mezcla «Danish» y
«Dansk», que agrupados por texto se contarían como dos idiomas. Como etiqueta se usa
la grafía más repetida, y un libro que liste las dos formas cuenta una sola vez.
Ambos casos tienen prueba.

Las cuatro tarjetas de barras comparten ya no solo la fila sino el marco completo
—título, contenido y nota de datos ausentes—, en BarChartCard.

Cadenas nuevas en inglés, español y danés.
Al final del perfil aparece «Editar página», que abre un diálogo con los diez
bloques: un interruptor para activarlos o desactivarlos y un asa para arrastrarlos y
cambiar su orden. Nada se aplica hasta pulsar Guardar, así que cerrar el diálogo deja
el perfil como estaba, y «Restablecer» vuelve al orden original.

Para que esto fuera posible, los bloques del perfil dejan de estar escritos en un
orden fijo dentro del LazyColumn y pasan a recorrerse desde una lista. La cabecera
—avatar, nombre y seguidores— se queda fuera a propósito: es la identidad del perfil
y no debe poder ocultarse.

La disposición se guarda en los ajustes como los ids separados por comas, con un «-»
delante de los ocultos. La lectura está preparada para el futuro: un id desconocido
—un bloque retirado más adelante— se ignora sin romper el resto, y los bloques que la
versión conoce pero no estaban guardados se añaden al final, visibles. Sin eso, al
añadir un bloque nuevo no aparecería a quien ya tuviera una disposición propia. Hay
además una prueba que falla si alguien renombra un id, porque eso invalidaría las
disposiciones ya guardadas en los dispositivos.

Los bloques desactivados conservan su posición en la lista para volver al mismo sitio
al reactivarlos.

En el arrastre, cada fila se identifica por su sección y no por su posición: sin esa
clave, al reordenar, el hueco pasaba a mostrar otra sección, Compose reutilizaba la
fila y reiniciaba el detector de gestos, de modo que el arrastre se perdía tras pasar
por encima de la primera vecina. Por lo mismo, la posición de origen se consulta en
cada movimiento en lugar de usar el índice capturado al componer. Las filas no pintan
fondo propio para no separarse del contenedor del diálogo; solo se tiñe la que se
está moviendo.

Cadenas nuevas en inglés, español y danés; Guardar y Cancelar se reutilizan de las ya
traducidas a los 17 idiomas.
Las tarjetas del perfil llegaban solo en inglés, español y danés y el resto de
idiomas recurría al inglés. Se completan las 29 cadenas y los 9 plurales en catalán,
checo, alemán, griego, finés, francés, gallego, italiano, neerlandés, polaco,
portugués, rumano, sueco y ucraniano, con lo que los 17 idiomas de la aplicación
quedan al día.

Los plurales siguen las reglas de cada idioma y no un one/other uniforme: polaco y
ucraniano llevan one/few/many, checo y rumano one/few/other, y el resto one/other.

Comprobado que ningún idioma pierde ni cambia un especificador de formato
(%1$s, %1$d, %2$d, %d) respecto al inglés, lo que provocaría un fallo en tiempo de
ejecución solo en ese idioma, y que todos los ficheros siguen siendo XML válido.

Aviso para quien revise: estas traducciones no las ha hecho un hablante nativo de
cada idioma. La estructura y los formatos son correctos, pero alguna expresión puede
mejorarse, sobre todo en las lenguas eslavas y en griego, finés y rumano.
Author
First-time contributor

Un apunte personal: se me acaba el periodo de vacaciones, así que a partir de ahora
voy a estar bastante menos activo y puede que tarde en responder a los comentarios de
revisión.

Si hay algo que quieras cambiar de este PR o del de las notificaciones, siéntete libre
de ajustarlo tú mismo, dividirlo o quedarte solo con la parte que te encaje — prefiero
eso a que se quede parado esperándome. Y si algo no te convence, ciérralo sin problema.

Gracias por la paciencia con tantos PRs seguidos y por lo bien que se trabaja sobre
este proyecto. Volveré cuando pueda.

Un apunte personal: se me acaba el periodo de vacaciones, así que a partir de ahora voy a estar bastante menos activo y puede que tarde en responder a los comentarios de revisión. Si hay algo que quieras cambiar de este PR o del de las notificaciones, siéntete libre de ajustarlo tú mismo, dividirlo o quedarte solo con la parte que te encaje — prefiero eso a que se quede parado esperándome. Y si algo no te convence, ciérralo sin problema. Gracias por la paciencia con tantos PRs seguidos y por lo bien que se trabaja sobre este proyecto. Volveré cuando pueda.
Owner

Muchísimas gracias por todo lo aportado. Gracias a ti, este proyecto está creciendo de forma acelerada. Me parece muy interesante estos cambios, los veo y los añado.
Yo también estoy muy liado con el trabajo y por eso ahora estoy menos tiempo con el proyecto, por eso te agradezco de corazón tus aportes

Muchísimas gracias por todo lo aportado. Gracias a ti, este proyecto está creciendo de forma acelerada. Me parece muy interesante estos cambios, los veo y los añado. Yo también estoy muy liado con el trabajo y por eso ahora estoy menos tiempo con el proyecto, por eso te agradezco de corazón tus aportes
ferlagod approved these changes 2026-07-25 15:55:18 +00:00
ferlagod closed this pull request 2026-07-25 16:07:23 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ferlagod/rocinante_android!18
No description provided.