Revision previa al envio a produccion. Cada punto se verifico en el codigo
antes de tocarlo; lo que ya estaba bien se dejo como estaba.
Ubicacion: se declaraba precision fina sin usarla
El unico consumidor de ubicacion pide `LocationAccuracy.low` y se queda solo
con el codigo ISO del pais, asi que `ACCESS_FINE_LOCATION` no aportaba nada. Y
contradecia la declaracion de Seguridad de los datos ya aprobada en Play, que
dice ubicacion APROXIMADA: declarar una cosa y pedir otra es precisamente lo
que se penaliza en revision.
Verificado que los manifiestos de geolocator_android y geocoding_android no
declaran permisos propios, asi que el merge no lo reinyecta y no hace falta
`tools:node="remove"`. El plugin construye su peticion en tiempo de ejecucion a
partir de lo declarado, de modo que con COARSE pide COARSE. Sin cambio
funcional: la deteccion de pais sigue igual.
El paywall vendia Android Auto como exclusivo, y ya no lo es
La etiqueta era literalmente "Android Auto", a secas. Pero el tier gratuito
recibio una carpeta navegable con emisoras reproducibles cuando hubo que
cumplir las guias del coche, asi que esa frase dejo de ser cierta. Ahora dice
que PRO añade el catalogo completo, favoritos, mis emisoras y musica local, y
aclara que gratis tiene las destacadas. Un paywall que promete lo que el tier
gratuito ya tiene expone a reclamacion y a que se cite en revision.
Microfono: se pide al activar el visualizador, no antes
Con una explicacion previa en los 13 idiomas, en vez de aparecer sin contexto.
Grabacion: uso privado de verdad, no solo en el aviso
La pantalla de grabaciones entregaba el fichero a cualquier aplicacion con
`Share.shareXFiles`. La intencion era abrirlo en un reproductor del propio
telefono, no redistribuirlo, y una cosa es copia privada y la otra no. Ahora
usa el `openFile` que ya existia -- FileProvider + ACTION_VIEW -- y avisa
cuando ningun reproductor del dispositivo puede abrirla, en vez de fallar en
silencio. Se añade ademas el aviso de uso privado en esa pantalla.
`recordingActionShare` la usaban DOS botones con significados distintos: el de
grabaciones, que mandaba el audio, y el del reproductor, que comparte el nombre
y la url de la emisora. Una clave, dos sentidos, y esa ambiguedad basto para
que al leer el codigo pareciera que solo se compartian enlaces. Separadas en
`stationActionShare` y `recordingActionOpenIn`.
La grabacion sigue siendo PRO. Lo que reduce el riesgo es que la copia no salga
del dispositivo, no regalar la funcion: los anuncios tambien son monetizacion.
Suite completa: 1587 pasan, 2 omitidos. flutter analyze mantiene los 5 avisos
preexistentes.
EstadoAlarmas, EstadoGrabacion and EstadoRadio defaulted `esPremium` to
`() => true`, so any construction site that forgot to wire entitlement
compiled fine and silently ran ungated — failing OPEN to premium and
disabling the paywall with no test able to catch it.
The parameter is now required with no default. Production wiring in
app.dart was already correct and is unchanged; the 184 pre-existing test
call sites now pass `() => true` explicitly, which is exactly the old
implicit default, so every assertion is untouched.
EstadoRadio has no gate of its own but constructs EstadoGrabacion, so it
inherits the same contract.
The one test that existed to pin the old default is renamed to describe
what it still covers (the premium path through iniciar() with no
duracion); its assertions are unchanged.
Adds a permanent, non-consumable premium unlock (EstadoEntitlement +
PuertoCompras/ServicioComprasPlayBilling) that removes ads and unlocks
alarm vacations, alarms past a 5-alarm free cap, recording start, and
full Android Auto browsing. The phone equalizer stays free for everyone.
- Entitlement is prefs-backed (compra_premium_v1), fail-open, and
resolvable headlessly via esPremiumPersistido() for the Android Auto
audio handler, which registers before runApp.
- Android Auto reduced mode keeps the real root folder labels for free
users; browsing into any of them (and playFromMediaId/playFromSearch/
skipToNext/skipToPrevious) is blocked at the getChildren/servicio_audio
choke points, with a locked "Función Premium" item as the backstop.
Current-station play/pause/stop stays untouched. A free -> premium
transition actively invalidates the head unit's cached browse tree.
- Ads (top banner + capped interstitial before adding a station or an
alarm) are gated behind entitlement via ServicioAnuncios, using
official Google test ad unit IDs pending AdMob provisioning.
- Alarm cap UX shows an explanatory message with a secondary unlock
action rather than a bare paywall jump; existing data is grandfathered.
- 4 new localization keys translated across all 13 supported locales.
Co-located tests use strict TDD (RED test before implementation) for
every new pure-logic unit; full existing suite passes unchanged.
Reported, with this on screen:
No se pudo iniciar la grabación: Invalid argument(s): Unsupported scheme
'content' in URI content://com.android.externalstorage.documents/tree/
primary%3AMusic/document/primary%3AMusic%2F...%2FNew Limit - Smile.mp3
The URI in that message is a local MP3, not a station.
PluriWaveAudioHandler._cambiarFuente sets `emisoraActual` for EVERY source
it plays, so a local track surfaces as an Emisora whose `url` is the SAF
content:// document URI it was opened from. EstadoGrabacion.iniciar only
checked for null, handed that straight to the recorder, and the HTTP
client failed with a message no user can act on.
"It used to work" is exactly right: before local music playback existed,
whatever was playing was always a real station, so the case could not
arise. The recorder never changed.
iniciar() now also requires a real network stream (esEmisoraGrabable) and
falls back to the existing "select a station first" message, which is the
correct guidance here -- recording a local file makes no sense anyway,
it is already on the device. No new l10n key, so no 13-locale churn for a
message that already says the right thing.
Tests: 1158 -> 1161.
- New EstadoGrabacion owns the recording service, subscription, directory/size preferences and open-file actions
- New EstadoBusqueda owns search, nearby stations, pagination and the min-bitrate filter
- New orden_emisoras.dart with the OrdenEmisoras enum, shared sorter and list identity memoization so context.select comparisons work on derived lists
- Large screens (inicio, buscar, favoritos, ajustes, reproductor) consume scoped selects/dedicated notifiers instead of root context.watch<EstadoRadio>, so audio buffer events no longer rebuild whole screens
- Remove all 15 TODO(S4b) compat members from EstadoRadio; consumers use the dedicated providers. EstadoRadio drops from ~1121 to 753 lines, keeping playback/stations/favorites orchestration
- 8 new tests including a rebuild-scoping probe (110 total green), flutter analyze clean