Commit Graph
198 Commits
Author SHA1 Message Date
FreeTLab 5bbf750b63 fix(ajustes): correct the gap between stacked settings groups
Issue 3 (feedback-pruebas): the prototype (t4:523/534/541) draws a
16px gap between the AUDIO/STATIONS/RECORDINGS/APPLICATION cards, not
12 -- a plain unwired literal that happened to collide with the
sectionGap/panelGap tokens' own value without actually citing the
prototype.
2026-07-30 22:08:40 +02:00
FreeTLab 7faf56900f fix(favoritos): stop the header padding from doubling up
Issue 3 (feedback-pruebas): ReorderableListView.padding wrapped
header/rows/footer with a single horizontal value (16), which doubled
up on top of PluriRootHeader's own internal inset -- landing the
title at 36px instead of the 20px every other root uses -- while also
applying card-tier padding to the flat FilaEmisoraPlana rows (row
tier, matching the same widget's fix on Buscar) and leaving the
populated-state top gap at an unwired 4 that didn't match this same
screen's own empty state (0) or the footer CTA's prototype value (8).

Zeroes the list-level padding and gives the header, chip strip, rows,
and footer CTA their own correctly-tiered insets instead.
2026-07-30 22:06:36 +02:00
FreeTLab fc866d7ec9 fix(buscar): correct the gaps between the filter row and the results
Issue 3 (partial): the results area had no top gap against the filter
row in one state and reused the horizontal constant for a vertical axis
in another. Applies the 3-tier scale properly -- row tier for
background-less placeholders, card tier for card states.

The rest of the app's spacing review is still outstanding.
2026-07-30 20:18:59 +02:00
FreeTLab 727e18737a fix(radio): persist the last-played station across restarts
EstadoRadio.emisoraActual only ever reflected in-memory state
(_emisoraSeleccionada or the live audio service), so stopping playback
and reopening the app left the Escuchar hero empty even though the
user had a station selected right before closing it.

Persist the station whenever it changes (reproducir(), and the
Android-Auto out-of-band reconciliation path) and restore it as
_emisoraSeleccionada on the next cold start, only when nothing is
already selected. This never touches the audio service directly: no
playback starts and estadoStream/estaSonando stay at their stopped
default, matching how every consumer already gates "is it playing" on
the playback-status stream rather than on emisoraActual itself.
2026-07-30 19:18:14 +02:00
FreeTLab c6ab295c54 fix(timer): show the live countdown in the sleep timer sheet
showPluriSleepTimerSheet already had a working countdown branch
(ServicioTimer.tiempoRestanteStream), but every preset and the custom
duration flow popped the sheet immediately after starting the timer --
so the countdown never rendered in the primary flow, only if the user
happened to reopen the sheet afterwards.

Stop popping the sheet on start; the existing Consumer<EstadoRadio>
already reacts to iniciarTimerDuracion's notifyListeners and swaps to
the countdown view live. Also make the sheet scroll-controlled: at a
realistic phone width the countdown's title + description + headline-
sized remaining-time text overflowed the default half-screen cap that
never mattered while the sheet always closed before that view could
render.
2026-07-30 19:14:15 +02:00
FreeTLab e75f010b98 fix(ajustes): stop settings row titles from wrapping and cutting off
FilaAjuste's title Text had no maxLines/overflow, and neither did its
trailing current-value Text. An unbounded value (e.g. a real station
name in "Emisora preferida") let the trailing Row claim unbounded
width, squeezing the title down until it wrapped across several lines
that the row's fixed height then cut short.

Constrain the title to a single ellipsized line and cap the trailing
value's width the same way. FilaAjuste backs all 12 settings rows, so
every row is protected, not just the one that happened to expose it.
2026-07-30 19:08:06 +02:00
FreeTLab c7e1a212ca fix(vacaciones): edit and delete vacation ranges
Vacaciones ranges could be created but never edited or removed --
EstadoAlarmas already had crearRangoVacaciones/eliminarRangoVacaciones
with no UI affordance reaching them, and no update path at all.

Add EstadoAlarmas.editarRangoVacaciones and wire tap-to-edit /
swipe-to-delete (with confirmation) onto every range card, mirroring
the alarm list's own Dismissible + confirm-dialog pattern exactly. This
covers the active-range hero too: a freshly created range is active
immediately and only ever renders there, never in the
scheduled/past lists, so it needed the same affordances or a user's
very first range could never be fixed.
2026-07-30 19:05:28 +02:00
FreeTLab d1a911e587 fix(widgets): share station-art fallback across every surface
TarjetaEmisora had the only good fallback for a station with no artwork --
a deterministic pick from 4 bundled illustrations with a gradient/glyph
last resort. FilaEmisoraPlana's flat rows, the Escuchar hero, the "Tus
emisoras" grid cell, the mini player and the full player each had their
own, separate, flat primaryContainer square instead.

Extract the good fallback into PluriStationArtFallback and use it from
every one of those call sites. The selection formula (asset order,
codeUnits-sum modulo) is preserved exactly, since navegacion_auto.dart
mirrors the same formula independently for Android Auto's own drawable
rotation.
2026-07-30 18:54:31 +02:00
FreeTLab 4be2156e58 fix(nav,favoritos): unclip the overflow menu and smooth the tab transition
Two user-reported bugs from on-device testing.

The favourites overflow menu carried `constraints: tightFor(38x42)`,
which sizes the POPUP rather than the button -- every item was clipped to
its first letter, so users saw "M" and "E" instead of the labels. The
existing test passed throughout because find.text matches a Text widget
whether or not it is visually clipped; the new guard measures the laid-out
width instead.

The bottom bar's ink splash had no shape, painting a hard square over the
icon, and the active tab's lift, dim, icon size and label all changed
instantly while the balloon slid -- the balloon glided and its contents
teleported. All four now share the balloon's duration and curve.
2026-07-30 18:26:25 +02:00
FreeTLab 42d35a2541 fix(buscar): filter pills, Idioma chip, results eyebrow, states screen
Audit 6.2 (t4:292-293): active filter pills are brand-teal tinted with
a 15px close glyph and radius 10 -- was Material's own Chip theming.

Audit 6.3 (t4:294-295): an "Idioma" entry chip is now always reachable
once a search is active -- there was no standalone entry point for
language filtering before (only bundled inside "Filtros"). Opens the
same existing filter sheet rather than a new idioma-only picker.

Audit 6.4 (t4:299): the results-count line now uses eyebrowLabel
styling -- was labelLarge (14/w800).

Audit 13.3/13.4 (t4:649-651): the search-results loading skeleton
gains the missing "BUSCANDO EMISORAS..." eyebrow, and its row gap is
now 4px, not 10.

Audit 13.5/13.6 (t4:657-668): a new purpose-built _TarjetaSinResultados
replaces PluriEmptyState ONLY in the search-no-results branch (that
shared widget is left untouched for its other unrelated call sites --
favorites empty state, the discovery grid). The title now quotes the
typed query ("Sin resultados para <<jazzz>>"), and the clear-filters
pill sits INSIDE the card. New ARB key searchNoResultsForQueryTitle.

Item 6.1 (compact 60px active-search header replacing the persistent
glass pill) is NOT implemented: existing tests confirmed the quality
(bitrate) filter has NO entry point anywhere in the app besides the
header's "Filtros" pill (added first via _abrirYSeleccionarCalidad
while a country filter is ALREADY active, i.e. mid active-search).
Hiding PluriRootHeader during active search would make quality
filtering unreachable -- a real capability regression the task's own
"presentation changes, capability does not" principle forbids.
Re-plumbing where that filter lives is a bigger change than this
visual-fidelity pass should make unilaterally.
2026-07-30 17:04:44 +02:00
FreeTLab f61b0b9163 fix(vacaciones): header Add action, info banner, dashed CTA, collapse
Audit 9b.1 (t4:446): a solid brand-teal "Add" header action -- the
prototype's own mid-page CTA (audit 9b.6, still present, now dashed)
is a SECOND, additional entry point, not a replacement.

Audit 9b.2 (t4:448): the teal explanatory banner ("alarms marked
pause-during-vacations won't ring...") is now ALWAYS visible -- never
rendered before. New ARB key vacationExplainerBanner, all 13 locales.

Audit 9b.6 (t4:487): the bottom CTA is now dashed-border with a
date_range glyph -- was a solid OutlinedButton with an add glyph.
Reuses the dashed-painter shape already established in
pantalla_favoritos.dart's custom-station CTA (audit 4.5), duplicated
rather than shared.

Audit 9b.7 (t4:489): "Rangos pasados" is now a collapsible row --
icon, title, count, chevron -- COLLAPSED by default, expanding on tap.
Was always fully expanded inline. Updated the pre-existing widget test
to tap-then-assert instead of asserting immediate visibility.

Item 9b.3 (the eyebrow literally reading "EN CURSO") is NOT
implemented: the eyebrow STYLING is already correct (audit 9b.4), and
the residual copy gap is a shared ARB string
(vacationSummaryActiveCountdown) also used compactly in
pantalla_alarmas.dart's vacation summary row -- diverging its wording
just for this screen's eyebrow, or forcing a shoutier tone into that
other compact usage, is not worth it for a trivial-rated copy nuance.
2026-07-30 16:53:04 +02:00
FreeTLab 36d7d5f692 fix(alarma-sonando): date line, additive snooze qualifier
Audit 9.4 (t4:419): "Lunes, 3 de agosto" now renders between the
schedule pill and the hero time -- purely additive, a new sibling
Text touching neither element. New formato_fechas.dart helper
fechaLargaConDiaSemana (locale-aware via DateFormat.MMMMEEEEd).

Audit 9.10 (t4:433): the highlighted snooze tile gains a "usual"
qualifier (new ARB key alarmSnoozeUsualLabel) alongside the original
flat label -- resolved DIFFERENTLY than its sibling 9.9 (permanently
rejected, id 2525): instead of splitting the flat
alarmSnoozeOptionLabel string into two differently-sized Text nodes
(which would make it vanish from the render tree the protected
dismiss-guard test locates via find.text), a SEPARATE small Text is
added alongside it. The original label stays a single, untouched Text
node, still inside the same FilledButton the guard taps.

Verified against the full ringing-screen test surface (34 tests
across 5 files, including the protected dismiss-guard file) -- all
green, dismiss-guard file reconfirmed byte-identical to main.
2026-07-30 16:45:29 +02:00
FreeTLab dcb415b3f8 fix(alarmas-editor): REPETIR eyebrow, grouped card, volume row
Audit 8.5 (t4:381): the "REPETIR" eyebrow above the weekday circles --
never rendered before. New ARB key alarmRepeatSectionLabel.

Audit 8.6 (t4:392-401): the station picker, volume, fade-in and
vacation toggle now share ONE bordered card with sangred divider lines
-- were four separately-spaced widgets. Restyled _CampoSelectorEmisora
as a flat nav row (icon/bold label/muted value/chevron, matching
FilaAjuste's own convention) so it fits inside the card instead of
drawing its own outlined InputDecoration chrome -- used by BOTH the
primary and fallback (Advanced) station fields. Snooze duration, "use
current station", and the Advanced section stay OUTSIDE the card,
exactly where they were (audit 8.8's own documented deliberate
extras). A transparent Material sits inside the card's coloured box so
the fade-in ListTile and the vacation SwitchListTile still paint their
ink/background correctly (the same class of constraint already
documented for PluriGlassSurface elsewhere in this codebase).

Audit 8.7 (t4:396): the volume row is now a compact 112px track with a
live percentage label -- was a bare full-width Slider with no visible
value. New ArtB key alarmVolumeLabel.

Item 8.8 (extra name/type-selector/snooze-selector/"use current
station"/Advanced fields) stays exactly as documented: deliberate,
required by the hard constraint that the editor keep its date field,
fallback-station picker and sound dropdown.
2026-07-30 16:42:04 +02:00
FreeTLab c7d137c82e fix(alarmas): New pill, banner layout, vacation pill, card recurrence
Audit 7.1 (t4:325): the "New" action is a solid brand-teal pill with a
plain add glyph -- was a tonal button with auto_awesome.

Audit 7.2 (t4:326-330): the next-alarm banner is warmCoral-tinted with
the Skip chip BESIDE the text on the same row -- was an opaque default
card with the skip action stacked below as an OutlinedButton.

Audit 7.3 (t4:332): the vacation row gains a trailing "d-d MON"
date-range pill for the active-or-next range, built from the existing
EstadoAlarmas.rangoVacacionesActivo/vacacionesProximas() accessors --
new formato_fechas.dart helper, no new state.

Audit 7.4 (t4:337-345): the alarm card now shows a recurrence label
next to the giant time (reusing the existing oneTimeOption/
dailyOption/weekdaysOption strings) and a themed station-icon slot
next to the station name. The real per-station favicon is NOT
rendered here -- same network-image hazard already documented for the
ringing screen's audit 9.2 (Emisora.favicon is a network URL;
Image.network hangs widget tests without a mocked HttpClient). The
custom switch shape (52x32/26px thumb) is also left as Switch.adaptive
-- a disclosed sub-gap, not a silent drop.

Item 7.6 (_AccesoDiagnostico, an Android-reliability debug row) is a
pre-approved addition not in the prototype -- informational only, no
action needed.
2026-07-30 16:30:20 +02:00
FreeTLab dcd8488874 fix(favoritos): header actions row, chip colours
Audit 4.1 (t4:216): the manage-groups (create_new_folder) and sort
(swap_vert) actions now live in PluriRootHeader's own actions slot as
two icon buttons -- were an ActionChip inside the chip strip plus a
PopupMenuButton sharing a Row with it. The prototype's own back arrow
stays absent (binding decision: this root keeps its bottom tab bar,
unlike the prototype's pushed-with-back-arrow shape) -- this closes
the remaining gap in what was a partial fix.

Audit 4.2 (t4:219-221): group filter chips are solid brand teal with
dark text when active, listSurface + a faint border when not -- was
Material's own ChoiceChip theming (electricMagenta@24% selected).

Updated pantalla_favoritos_test.dart's two "Manage lists" text finders
to locate the relocated action by key instead (the action is now an
icon-only IconButton with a tooltip, not a labelled chip).
2026-07-30 16:22:12 +02:00
FreeTLab 94b1e901d1 fix(paises): eyebrows outside the card, country count, header search
Audit 5.4 (t4:254/260): "Tus idiomas" and "Todos" eyebrows now sit
OUTSIDE any card at title-tier (20px) padding, styled with
eyebrowLabel -- were titleMedium w900 inside a PluriGlassSurface.
"Todos" also gains its missing total count ("{title} · {count}").

Audit 5.7 (t4:252): the header gained a real `search` action -- toggles
an inline TextField that filters the country list by name or ISO
code, not a decorative no-op button. New ARB key countriesSearchHint.

Item 5.6 (station count as a subtitle line, not a trailing widget) was
already fixed as an undocumented side effect of Tier3's row rebuild
(3303bd3) -- reconfirmed by direct read, no change needed here.
2026-07-30 16:18:44 +02:00
FreeTLab 533896b9fa fix(ecualizador): header switch, banner radius, preset chips, glow thumb
Audit 11.1 (t4:566): the master enable switch moves to
PluriPushScaffold's header actions -- was the body's first
SwitchListTile row. Its realtime/pending explainer subtitle stays
behind as a plain caption so no information is lost.

Audit 11.2 (t4:571): explainer banner radius is 16 (a local one-off,
matching neither of the 3 named tokens), not radiusSm's 14.

Audit 11.3 (t4:574-577): preset chips are solid brand-teal with dark
text when active, listSurface + a faint border when not -- was
Material's own ChoiceChip theming (primaryContainer/grey).

Audit 11.5 (t4:585): band sliders use a new 20x20 _GlowSliderThumbShape
(brand-teal blurred glow + solid thumb), replacing the Material
default round thumb.

Audit 11.7 (t4:584): the dB label is brand teal at 90% alpha, not
liveGreen -- a leftover wrong colour family 11.6's slider-only fix
never touched.

Updated 2 pre-existing tests (pantalla_ajustes_ecualizador_test.dart,
pantalla_ajustes_test.dart) to locate the enable switch by key instead
of by the "Enable equalizer" text it no longer renders next to.
2026-07-30 16:14:11 +02:00
FreeTLab b5b9829faa fix(ajustes): group eyebrow outside the card, per-row accent icons
Audit 10.2 (t4:511): GrupoAjustes's eyebrow now sits OUTSIDE the
PluriGlassSurface, at title-tier (20px) padding -- it used to share
the card's own 16px content padding.

Audit 10.5 (t4:514/526): FilaAjuste gains an optional iconColor.
Wired on the two rows the prototype actually accents: AUDIO's
Ecualizador (brand cyan) and STATIONS' Grupos de favoritos (warmCoral).

Item 10.10 (two extra AUDIO rows -- "Calidad de streaming" and
"Reproduccion sin interrupciones") is NOT implemented: neither concept
has any backing state or service anywhere in this app today. Building
them for real means inventing two new persisted settings, and a
genuine streaming-quality preference would plausibly need to touch
the protected servicio_audio.dart to actually affect playback --
out of scope for a visual-fidelity pass. Decorative rows that open
nothing would be dead UI, which is worse than leaving the gap
disclosed.
2026-07-30 16:05:28 +02:00
FreeTLab e036f99a61 fix(bienvenida,grabaciones): headline size, CTA radius, storage caption
Screen 14 / audit 14.3+14.7: welcome headline now 34px/ls-1.2 (was
headlineMedium's 28/ls-1.0), CTA restyled to a radius-18 rounded
rectangle instead of Material 3's default StadiumBorder (t4:696,715).

Screen 12 / audit 12.2+12.3: the recordings storage card now shows the
bold "X of Y used" headline ABOVE a 6px/radius-3 bar, followed by a
real folder-path + purge-policy caption below it -- was the bar first
with the "used" string as its only (small, generic) caption (t4:613).
New ARB key recordingsLibraryStorageFolderCaption, translated to all
13 locales.

Item 12.5 (folder/max-size settings living behind the header action
instead of inline on this screen) is a deliberate structural split
from WU15b; the audit itself scopes it out of this pass.
2026-07-30 16:00:30 +02:00
FreeTLab 01615f9751 fix(escuchar,reproductor): add the section-heading token and screen 1-3 polish
Audit S7 plus the open items on screens 1-3. Escuchar gains the
prototype's own 52px "now listening" eyebrow header (t4:53), which is
structurally different from the 56px title row every other root uses
(t4:325) -- the earlier wiring test asserted PluriRootHeader on all five
roots, an over-broad premise now corrected to guard what actually holds:
no AppBar, and the sleep-timer action still reachable.
2026-07-30 14:48:06 +02:00
FreeTLab 7ff156852f fix(bienvenida): match body/bullet type sizes and clip the logo mark
Tier 4 visual fidelity, audit 14.4/14.5/14.6/14.8 (t4 lines 693-701):
body copy is 14.5px (was bodyMedium's 14), bullet titles are
13.5px/w800 and subtitles 11.5px (was 14/w800 and 12), the gap between
bullets tightens from 16 to 12, and the 76x76 logo mark is now clipped
to a 20px rounded rect instead of painted square.
2026-07-30 13:02:40 +02:00
FreeTLab 038ec7f3b8 fix(alarma-sonando): pulsing halo, hero-time metrics, art size, snooze icon
Tier 4 visual fidelity, audit 9.1/9.5/9.6/9.8 (t4 lines 411-428):

- 9.1: new _HaloPulsante renders the prototype's 420px amber radial
  gradient behind the hero content. Deliberately BOUNDED (one
  grow-and-settle cycle), not the prototype's literal `infinite` CSS
  animation: this screen's protected dismiss-guard test calls
  pumpAndSettle() after every mount/interaction, and a genuinely
  infinite AnimationController.repeat() would hang it forever with no
  way to fix it. Skips its Animate wrapper under reduced motion, same
  as every other entry animation in this app.
- 9.5: the hero time gets a local letterSpacing/height override
  (-4/0.95) instead of the shared heroTime token's -2.0/1.0 — the
  alarm editor's hour block is the token's other consumer and still
  wants height:1, so the shared style is untouched.
- 9.6: ringing-screen art grows from 168/radiusLg(30) to 180/36 (t4
  line 421), a local constant like the existing stop-button radius.
- 9.8: the POSPONER eyebrow regains its 19px warmCoral snooze icon.

All 5 protected files (including the dismiss-guard test) remain
empty-diff against main.
2026-07-30 12:58:19 +02:00
FreeTLab 8fcb734ab0 fix(alarmas): tighten the gap between stacked alarm cards to 10px
Tier 4 visual fidelity, audit 7.5 (t4 line 334): the prototype stacks
alarm cards with a 10px gap; the build used 12. Each gap is now keyed
per alarm id so a guard test can pin the exact value between two known
cards.
2026-07-30 12:45:41 +02:00
FreeTLab ca2400f0d4 fix(favoritos): restyle the dashed custom-station CTA to spec
Tier 4 visual fidelity, audit 4.5 (t4 line 235): the border and the
label/icon now use the prototype's two DIFFERENT opacities
(rgba(255,255,255,.16) stroke vs rgba(242,247,250,.6) text/icon,
previously one shared 50% colour for both), padding is a uniform 14
instead of symmetric(18,16), and the icon is a plain add glyph at 20px
instead of add_circle_outline_rounded.
2026-07-30 12:40:59 +02:00
FreeTLab 163241d4a1 fix(inicio): add the favourites count badge and plain-text Ver todas
Tier 4 visual fidelity, audit 1.11/1.12 (t4 line 80): a pill badge next
to "Tus emisoras" now shows the TOTAL favorite count (not the 8-capped
grid size), and "Ver todas" is plain 12px/w800 brand-teal text instead
of a Material TextButton with its own padding and splash.

Item 1.4 (hero art radius) was already fixed as a side effect of an
earlier commit — no change needed here.
2026-07-30 12:36:27 +02:00
FreeTLab 56123ea51b fix(ajustes): inset row dividers and shrink icon/title/chevron
Tier 4 visual fidelity, audit S10/10.6/10.8/10.9 (t4 lines 514-516):
GrupoAjustes' row divider now indents 47px instead of running
full-bleed, FilaAjuste's leading icon drops from Material's 24px
default to 21px, the row title is a local 14px override of cardTitle
(14.5), and the chevron shrinks to 19px at 40% opacity instead of the
24px full-opacity default.
2026-07-30 12:32:10 +02:00
FreeTLab 8ecfc928b3 fix(reproductor): move the quality row last and size the play button at 78
Audit 2.4 and 2.6: the quality row sat right after the subtitle, four
positions before where the prototype puts it (t4:114-140), and the play
button was 72 with a 40 icon instead of 78/42 (t4:127).
2026-07-30 12:14:51 +02:00
FreeTLab 3303bd387c fix(paises,buscar): tappable ISO rows and a reconnect card
Item 24 / audit 5.1-5.3, 5.5 (t4:255-269): Paises' "Tus idiomas" was
a Wrap of non-interactive Chips, and the full list was a plain
ListTile with no ISO column and no onTap. Both now share one tappable
row (ISO code, name, station count, chevron); the first "Tus idiomas"
row gets the prototype's teal-tinted highlight. The one production
call site wires the tap to filter Buscar by that country's code and
pop back -- EstadoBusqueda.buscar(pais: ...) already accepts any ISO
alpha-2 code, not just the ~10 presets in the filter sheet.

Item 25 / audit 13.2 (t4:643-646): a reconnect card (rotating ring,
station name, "Reconectando...", a stop affordance) replaces the
complete absence of any reconnect signal outside a word in the mini
player. Ships WITHOUT the prototype's attempt counter: the only live
ControladorReconexion instance is a private field of
PluriWaveAudioHandler inside servicio_audio.dart, a file this task
requires stay byte-identical to main, and nothing else re-exposes it.
Reconstructing a count from estadoStream's reconectando emissions
would not be faithful (the stream can emit it many times per actual
backoff attempt), so that was deliberately not attempted.
2026-07-30 11:48:25 +02:00
FreeTLab 955682271c fix(favoritos,grabaciones): replace glass cards with flat rows
Item 23 / audit 4.3, 12.4 (t4:226-232, 616-619): Favoritos and
Grabaciones rows were full glass cards / ListTiles with two stacked
buttons and no artwork slot. Replace with flat, background-less rows
via a new shared FilaEmisoraPlana widget (square art, name+meta, a
circular play affordance) plus a bespoke Grabaciones row (44x12
placeholder art -- recordings carry no per-station favicon, so this
is a themed fallback, not invented artwork).

Favoritos keeps "Move to list" / "Remove from favorites" behind an
overflow menu (same underlying methods, unchanged) instead of two
always-visible buttons, since dropping either would be a functional
regression the prototype's own row doesn't have to solve for.

Also 12.1 (t4:610): the Grabaciones header action is folder_open, not
a generic gear.
2026-07-30 11:46:55 +02:00
FreeTLab 4537497e83 fix(chrome): rebuild MiniReproductor as a full-bleed opaque bar with art
Item 22 / audit 3.6 (t4:184-188): the mini player was a floating
999-radius glass pill with no artwork. Replace it with a 60px opaque
bar (listSurface at .97 alpha), full-bleed edge to edge, showing the
station's square 42x42 artwork instead of the abstract playing-bars
indicator. app.dart no longer wraps the bar in the balloon nav's own
8px side margin, so it now spans the full width.

MiniReproductor.altura is re-measured (72 -> 60) now that the bar's
content height is fixed by construction; PluriLayout.bottomChromeInset
derives from it as before. Both the S3-R3 configurarLocalizaciones
guard and the altura measurement test still pass unmodified.
2026-07-30 10:38:59 +02:00
FreeTLab 2b28c4daed fix(vacaciones): replace the progress bar with the prototype's date pair
Item 21 / audit 9b.4-9b.5 (t4:451-462, 469-479): the active vacation
range showed a LinearProgressIndicator the prototype never draws.
Replace it with the screen's real signature element -- a start/end
date pair (day+month, weekday, connector rule) -- reused with a flat
connector for the "programados"/"pasados" rows, replacing their plain
ListTiles with proper cards.
2026-07-30 10:25:07 +02:00
FreeTLab 2255374291 fix(alarmas): restyle the editor sheet as the prototype draws it
Audit 8.1-8.4: opaque bottom-anchored sheet with a grab handle, a framed
time card and 7 circular day buttons.

The date field, fallback-station picker and sound dropdown all stay
reachable -- the prototype omits them, but presentation changes never
remove capability.
2026-07-30 10:08:54 +02:00
FreeTLab 653848899f fix(eq): split the equalizer body into 4 cards, add Restablecer a plano
Audit 11.10/11.8 (t4 lines 570-602): the prototype draws the explainer
banner, the band sliders, "Salida activa + Guardar como preset +
Restablecer a plano" and "Emisoras con ajuste propio" as 4 separate
cards. The build nested everything in one outer PluriGlassSurface, so
it read as a single merged block, and had no reset-to-flat action at
all.

Removes the outer wrapper from _CuerpoEcualizador. EcualizadorWidget
already draws its own card (design ADR-5, unchanged); the explainer
banner already has its own tinted background (unchanged); "Emisoras
con ajuste propio" now wraps itself in its own PluriGlassSurface; a
new _TarjetaSalidaYAcciones groups Salida activa with two tappable
rows — "Guardar como preset" (relocated from a floating end-aligned
button) and the new "Restablecer a plano", which applies
PresetEcualizador.flat via the existing EstadoEcualizador.cambiarPreset
— the same call the "Plano" preset chip already makes, not a new
capability.

The 5-slider band count, servicio_ecualizador.dart and
preset_ecualizador.dart are untouched.
2026-07-30 00:57:29 +02:00
FreeTLab e5d461af53 fix(buscar): build the 2x2 "Explorar por" grid, add Novedades
Audit 3.2 (t4 lines 162-171): the prototype groups Países, Géneros,
Tendencias and the entirely-missing Novedades into one 2x2 entry-point
grid. The build had them as three unrelated always-visible widgets
(a Países ListTile, a Géneros FilterChip Wrap, a Tendencias ActionChip
strip) and no Novedades entry at all.

Replaces all three with a single grid section, capability-preserving:
Países still pushes PantallaPaises; Géneros and Tendencias now open
their exact existing content in a picker sheet instead of always-on-
screen (Géneros auto-closes on selection, matching this screen's other
single-choice filter sheets; Tendencias stays open, a browse list, not
a filter). Novedades re-triggers the existing discovery refresh, since
no distinct "new stations" feed exists anywhere in the domain.

New ARB key set (exploreByTitle/exploreTrendingTitle/
exploreTrendingSubtitle/exploreNewTitle/exploreNewSubtitle) translated
across all 13 locales with zero anti-copy allowlist entries needed.

Also fixes a pre-existing PluriEmptyState overflow this restructure
exposed (unrelated to the grid itself, confirmed via isolated repro):
wraps its Column in a SingleChildScrollView so a too-tall title/
subtitle scrolls instead of throwing a hard RenderFlex overflow.
2026-07-30 00:43:27 +02:00
FreeTLab a49b2b6519 fix(inicio): render Tus emisoras as a 2-column grid of 8
Audit 1.10 (t4 lines 82-88): the prototype shows a 2x2+ grid of 8
favorite stations; the build was a 260px-wide horizontal strip capped
at 6.

Raises the cap to 8 and replaces the horizontal ListView with a
GridView.builder (2 columns, gap 10). Adds a dedicated _CeldaTusEmisoras
cell (44px square art radius 11, name + genre) rather than reusing
TarjetaEmisora(esCompacta: true), since that widget always renders a
favorite button and a live badge that this prototype cell never draws.
2026-07-30 00:10:11 +02:00
FreeTLab fcba592352 fix(visualizador): add a discrete-bar rendering mode
Audit 1.7/2.5 (t4 lines 66-68, 120-122): the prototype draws 30
discrete bottom-anchored bars (radius 2, vertical gradient); the
build's VisualizadorAudio painted a single continuous oscilloscope
stroke, so its `barras: 30` parameter never produced bars.

Adds an opt-in `barrasDiscretas` mode (default false, byte-identical
continuous rendering preserved for any other caller) plus a
`gradienteFinAlpha` knob for the two screens' differing gradient
end-alpha (.3 vs .45). Wires it into the Escuchar hero and the full
player, correcting the player's bar count/height to match the
prototype (26->30 bars, 46->40px) at the same time.
2026-07-30 00:04:03 +02:00
FreeTLab e94e64faca fix(bienvenida): bottom-anchor content and add the blurred backdrop
Audit 14.2 (t4 lines 693/706): content was top-anchored in a plain
SingleChildScrollView; the prototype anchors it to the bottom with two
flexible spacers, so the CTA sits at the screen edge. Restructured as
a scrollable Expanded region (safe on short screens / large text
scale) with the CTA pinned below it, always at the bottom.

Audit 14.1 (t4 lines 687-689): added the full-bleed blurred banner
backdrop, reusing aurora_wave_banner.png (the same asset the deleted
PluriScreenHeader used) since this app has no bundled equivalent of
the prototype's mockup-only banner.jpg.

No monetization content touched (PRO pill, "14 días", pricing card,
free-version link all remain absent per the binding no-monetization
decision).
2026-07-29 23:41:35 +02:00
FreeTLab 684e8e8ff9 fix(buscar): rebuild the offline banner with offlineAccent
Audit 13.1 (t4 line 641): the banner rendered wifi_off in
colorScheme.error (red) inside a generic glass card. Rebuilt using
offlineAccent (#E8879A) — a token that existed since Tier 1 with no
consumer — for the tint, border and icon, plus the prototype's title/
detail layout and a restyled Retry chip. New offlineBannerTitle key
translated to all 13 locales.
2026-07-29 23:40:38 +02:00
FreeTLab e1505d7aaa fix(escuchar): show the station meta line in the hero
Audit 1.6 (t4 line 62): the Escuchar hero never rendered "genre ·
country · bitrate kbps" between the station name and the visualizer.
Built from fields Emisora already carries (tags/pais/bitrate) — no
new fields, no service calls — omitting gracefully whatever a station
lacks.
2026-07-29 23:34:07 +02:00
FreeTLab 9840205a83 fix(alarma-sonando): add the schedule pill and restyle the station name
Audit 9.3 (t4 line 415): the ringing screen never showed its schedule
pill. Reuses the alarmScheduleOnce/alarmScheduleWeekdays ARB keys that
already existed with no consumer, plus a new alarmScheduleDaily key
(translated to all 13 locales) for the recurring-alarm case shown in
the prototype.

Audit 9.7 (t4 line 423): the station name was cardTitle (14.5px/w700),
5.5px and 100 weight units under the prototype's 20px/w800.

Audit 9.9 (snooze tiles as number-over-unit) is intentionally NOT
included in this commit: it would require the tile to stop rendering
as a single flat "{minutes} min" Text node, which is exactly what
pantalla_alarma_sonando_dismiss_guard_test.dart taps via
find.text(l10n.alarmSnoozeOptionLabel(N)) in four places. That file
must stay untouched, so this restyle is deferred pending a decision
on how to restructure the tap target safely.
2026-07-29 23:33:17 +02:00
FreeTLab dcb716cbc3 fix(alarma-sonando): restyle Stop as a neutral translucent surface
Audit 9.11 (t4 line 434): the Stop button was a FilledButton in
colorScheme.primary (brand cyan), the wrong colour family for the
largest element on the ringing screen. The prototype draws a neutral
rgba(255,255,255,.08) surface with a rgba(255,255,255,.16) border and
radius 24 (none of PluriWaveTokens' three named radii).
2026-07-29 23:27:28 +02:00
FreeTLab cd77ec256e fix(escuchar): restore the prototype's 132px hero artwork
Audit 1.3: the hero art shipped at 84 where the prototype draws 132
(t4 line 56), with a 24 corner radius rather than the shared radiusMd.

Adds a dimension guard. Every existing test on this screen asserts
behaviour only, which is why an artwork 36% too small went unnoticed.
2026-07-29 23:04:40 +02:00
FreeTLab 615a5aac92 fix(eq): restore the prototype's band height and brand-teal fill
Audit 11.4 and 11.6: the band column was 152 instead of 280 (t4 line
581), leaving the sliders 46% short, and the fill read liveGreen -- the
LIVE badge colour -- instead of brand teal (t4 line 585).

Adds two guards. The existing tests only asserted the band COUNT, which
is exactly why both values could drift unnoticed.
2026-07-29 23:01:20 +02:00
FreeTLab 97e38becfe fix(chrome): retire PluriScreenHeader, it has no equivalent in the prototype
PluriScreenHeader was a 38-radius glass hero: an aurora banner at 24%
opacity, a black-to-transparent scrim, two radial orbs, a 120px app-mark
watermark and a 56px tri-gradient glyph badge. None of it is in the
prototype (t4) -- every root's title is plain text in its own 56px
row, which PluriRootHeader (S1) already provides. Delete the class
(and its now-orphaned _Orb helper) and its four call sites (Buscar,
Favoritos x2, Alarmas, Ajustes landed with S8).

Two of those call sites carried the hero's only functional bit besides
the title, so PluriRootHeader gains an `actions` slot (rendered before
the shared bedtime button) to keep them reachable:
  - Alarmas' create-alarm button (FilledButton.tonalIcon, unchanged
    shape, just relocated)
  - Buscar's filters entry point (the same tappable PluriStatusPill,
    just relocated)
Every other retired trailing pill (Ajustes' "Secure" status,
Favoritos' collection-count badges, Alarmas' alarm-count badge) was
purely decorative and matches the prototype by simply disappearing.

S2, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 21:29:49 +02:00
FreeTLab 7ebe0b77a4 fix(layout): introduce the prototype's 3-tier horizontal padding scale
The prototype runs three horizontal padding tiers (t4): 20px for
section titles/eyebrows (lines 153, 254, 299, 511), 16px for cards
(lines 327, 512, 610), 12px for background-less list rows (lines 174,
226, 301). The build had collapsed all three into a single
PluriLayout.horizontal = 16, used everywhere regardless of context.

Add PluriLayout.titleHorizontal (20) and PluriLayout.rowHorizontal
(12) alongside the existing `horizontal` (16, unchanged -- it already
covers the card tier). horizontal keeps every one of its ~30 existing
call sites unchanged.

Committed ahead of S2 (item 6 in this pass) because that item's
PluriRootHeader edit reuses titleHorizontal/rowHorizontal for the
header's own padding, matching the prototype's own header spec exactly
(e.g. Alarmas padding:0 12px 0 20px) -- a real dependency, not just
numbering.

S5, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 21:28:48 +02:00
FreeTLab e0164f68b5 fix(ajustes): show each settings row's current value
The prototype puts a trailing current-value string on nearly every
settings row (t4 lines 512-539, 625 -- "3 guardados", "Alfabetico",
"Espanol", "7 . 84 MB"). FilaAjuste only accepted icon/titulo/onTap,
so every row was value-blind.

Add an optional `valor` slot to FilaAjuste (13px, rgba(242,247,250,.55),
rendered before the chevron). Wire 8 of the 12 built rows to state
already available at the settings root: equalizer on/off, sleep-timer
active, favourite-group count, preferred station name, custom-station
count, sort order, recordings count-and-size (FutureBuilder over
EstadoGrabacion.listarGrabaciones), and the current language (hoisted
pantalla_ajustes_idioma.dart's native-name list to module level so the
root can read it without duplicating it). Salida de audio, Musica
local, Backup and Info's version are left without a value -- each
lacks a low-risk, deterministically-testable data source (see the
apply-progress note for the reason per row).

Reading EstadoRadio for these values through a root `context.watch`
would rebuild the whole settings list -- including the Grabaciones
FutureBuilder's disk read -- on every unrelated audio notification;
this follows the codebase's existing S4-R5 convention of narrow
`context.select` per field instead.

Ajustes' own PluriRootHeader/PluriScreenHeader edit (S2 in this same
pass) landed in this commit too, since both touched the same header
block in pantalla_ajustes.dart at the same time.

S8, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 21:26:48 +02:00
FreeTLab f5a211492a fix(typography): bake the prototype colour into eyebrowLabel
The prototype's eyebrows are always rgba(242,247,250,.42) (t4 lines
254, 299, 381, 450, 511, 660). eyebrowLabel set size/weight/letter-
spacing only, no colour, so every call site (settings group headers,
vacation section titles, the ringing screen's snooze label) rendered
at whatever full-opacity default the ambient text theme resolved to.

One factory-level change fixes every current and future consumer of
PluriWaveTypography.eyebrowLabel — no call site needs editing.

S9, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 20:34:09 +02:00
FreeTLab d0660a4966 fix(stations): replace the ringed circle thumbnail with a plain square
The prototype's station thumbnail is a plain 44-48px square, radius
11/12, with no ring or glow (t4 lines 84, 175, 227, 302, 614).
TarjetaEmisora's compact row variant instead painted a 58x58 circle
wrapped in a SweepGradient ring (magenta/cyan/coral) and a 22-blur
glow behind a 50x50 ClipRRect(18).

Replace it with a 48x48 ClipRRect(12) square. Update the loading
shimmer placeholder to match (was also a 58x58 circle block).

S6, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 20:29:31 +02:00
FreeTLab 5a5f1655a9 fix(surfaces): make list/card surfaces opaque by default, fix card radius
The prototype states an explicit system rule (t4 line 40): "opaque list
surface #102532, glass only in the chrome and in the active card."
PluriGlassSurface backs nearly every card/row in the app, but always
rendered translucent + blurred (glassSurface, blur 18) — the listSurface
token (#102532) existed since WU1 but was only ever used at reduced
alpha, never opaquely.

Add a `glass` flag to PluriGlassSurface, defaulting to false: an opaque
listSurface fill with no BackdropFilter. Chrome (MiniReproductor) and
the active/now-playing card (Escuchar's hero, when a station is
playing) opt into the old translucent look via `glass: true` — every
other of the ~26 call sites needs no change and now renders opaquely.

Also fix the card radius: the prototype's dominant card radius is 18
(t4 lines 512, 613, 715, 133); radiusMd was 22, a systematic +4px drift
across every card that defaults to it. Retune TarjetaEmisora's own
radius ternary so its compact (row) variant uses the dominant row
radius, 14, instead of inheriting the card radius.

S3+S4, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 20:23:55 +02:00
FreeTLab 78a7415dd8 fix(chrome): drop the global AppBar, give each root its own title row
The prototype (t4) draws no global app bar anywhere: every root paints
a plain ~56px title row inside its own content instead (Alarmas line
325, Ajustes line 511, Explorar line 641). app.dart wrapped every tab
in PluriWaveScaffold(appBar: AppBar(title: Text(appTitle), ...)),
adding 56dp of chrome and a "PluriWave" title the prototype never
shows.

Add PluriRootHeader, a shared 56px title-row widget reused by all 5
roots. Extract app.dart's old _mostrarTimerDialog (only reachable from
the removed AppBar action) into a free function,
showPluriSleepTimerSheet, so every root's header can open the same
sheet directly and the sleep-timer feature stays reachable from every
tab with no behaviour change.

S1, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 20:17:24 +02:00