Commit Graph
276 Commits
Author SHA1 Message Date
ShanaiaBot 08cae2a5d4 chore: bump version to 1.0.1+105 [ci skip] 2026-07-16 16:29:34 +02:00
Javier Bautista Fernández 07c6e32af0 docs(auto): android auto research guide and sdd artifacts for android-auto-media
Build & Deploy PluriWave / Análisis de código (push) Successful in 26s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m22s
2026-07-16 16:28:54 +02:00
Javier Bautista Fernández 35bb180612 feat(auto): browsable Android Auto media tree with play-by-id [size:exception]
Expose PluriWave to Android Auto (projected) as a media app:

- Declare car media support (automotive_app_desc.xml + manifest meta-data)
  so Android Auto discovers the existing MediaBrowserService.
- New navegacion_auto.dart: ConstructorArbolAuto builds the browse tree
  (Favoritos / Todas las emisoras / Mis emisoras, 50-item cap, stable
  emisora:<id> media ids), reproducirPorMediaId routes a car tap to the
  existing playMediaItem pipeline, FuenteEmisorasAutoLocal serves the tree
  cold-start-safe (local favorites/custom stations before Flutter UI runs).
- PluriWaveAudioHandler overrides getChildren/getMediaItem/playFromMediaId
  as thin delegations; playback pipeline untouched.
- EstadoRadio pushes live station snapshots to the browse source and
  reconciles the selected station when playback starts from the car.
- Every playable item ships title + artUri; stations without logo fall
  back to a bundled default art (android.resource://).

Tests: 52/52 green (10 new navegacion_auto, 2 new estado_radio, plus
audio safety-net suites). Handler overrides and native XML are
static-review-only (no Android build env). Size exception approved for a
single reviewable commit.
2026-07-16 16:28:44 +02:00
ShanaiaBot 43781274ce chore: bump version to 1.0.0+104 [ci skip] 2026-07-15 15:44:16 +02:00
Javier Bautista Fernández 03de273369 chore(release): promote to 1.0.0 stable [version set]
Build & Deploy PluriWave / Análisis de código (push) Successful in 47s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m23s
The app is stable, so mark this as the 1.0.0 milestone. The CI bump step
now honors a [version set] marker: when present it ships the pinned semver
as-is and only advances the build number (Play requires it monotonic),
instead of the automatic patch bump that cannot cross the 0.x -> 1.0.0
boundary. Normal commits keep auto-incrementing the patch (1.0.1, 1.0.2, ...).
2026-07-15 15:43:16 +02:00
ShanaiaBot f1bb54d25a chore: bump version to 0.1.102+103 [ci skip] 2026-07-14 15:19:47 +02:00
Javier Bautista Fernández 38d78fc4f8 fix(alarm): honor the persisted trigger on reschedule so app updates stop re-arming the wrong day
Build & Deploy PluriWave / Análisis de código (push) Successful in 40s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
reschedulePersistedAlarms re-armed every stored alarm through the native
recompute engine (trustDartTrigger defaulted to false). That engine can
diverge from Dart's next-occurrence verdict and arm the alarm for the
wrong day, so it silently never fires. Because this runs on boot, unlock
and ACTION_MY_PACKAGE_REPLACED (which fires on every app install), each
new build re-broke correctly-armed alarms while snooze kept working
(snooze never touches the recompute for its trigger value).

Trust the persisted trigger (Dart's own verdict, saved when the alarm was
last armed) whenever it is still in the future; a genuinely stale past
trigger still falls back to the native recompute inside scheduleSpec.
2026-07-14 15:18:38 +02:00
ShanaiaBot 448fbec354 chore: bump version to 0.1.101+102 [ci skip] 2026-07-12 23:33:36 +02:00
FreeTLab d8e67a5204 fix(alarm): make all date math wall-clock correct across DST and timezone changes
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m46s
Full time-domain audit (three shipped date bugs prompted it) found one
root cause and two latent travel defects, all now fixed:

Day-stepping used add(Duration(days: 1)), which shifts the absolute
instant by exactly 86400s — documented Dart behavior (sdk#47666), so
crossing a DST transition drifted the wall hour by +-1h permanently
for the rest of the candidate scan (verified: 2026-03-28 07:30
Europe/Madrid + "1 day" = 08:30). The native Calendar engine preserves
wall time, and the single-authority fix made the drifted Dart verdict
win. Candidates now advance by calendar reconstruction (_siguienteDia:
DateTime(y, m, d+1, hora, minuto)), the same wall-clock-preserving
semantics as Calendar.add(DAY_OF_YEAR, 1) plus AOSP DeskClock's
defensive hour/minute re-assertion, keeping both engines in agreement
through any transition.

Instant-valued fields (snoozeHasta/snoozeOrigen/proximaEjecucion/
ultimaEjecucionGestionada/creadaEn/actualizadaEn) serialized as
offset-less local ISO, so re-parsing after a device timezone change
reinterpreted the same wall fields as a different instant. They now
serialize as UTC ("Z"); reads normalize to local, and legacy
offset-less payloads parse identically — no migration. fechaUnica
stays local on purpose: it is a wall-clock date.

One-shot alarms sent fechaUnica's midnight epoch to the native side,
whose boot/travel re-arm derives the calendar day back from it in the
CURRENT zone — a westward shift rolled the date to the previous day.
The channel now anchors the date at local noon, keeping it stable
across real-world zone shifts.

Property tests lock the no-drift guarantee (400 daily / 200 weekday
iterations must all land exactly at hora:minuto — on DST-observing
dev machines this crosses real transitions), plus UTC round-trip,
legacy-payload compatibility, and wall-date preservation tests.
2026-07-12 23:32:32 +02:00
ShanaiaBot e84cd2d7ed chore: bump version to 0.1.100+101 [ci skip] 2026-07-12 23:18:26 +02:00
FreeTLab 9c7cf4e261 fix(alarm): anchor snooze to the ringing occurrence, never a future one
Build & Deploy PluriWave / Análisis de código (push) Successful in 37s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m49s
posponerAlarma anchored the snooze to snoozeOrigen ?? proximaEjecucion,
but once the native fire path works, the fire-time sync records the
handled occurrence and recalculation advances proximaEjecucion to the
NEXT day before the user can even tap snooze on the still-ringing
screen. "Posponer 3" therefore armed the snooze a full day out
(captured on-device: snoozeCountdown remaining=1443 minutes). The bug
was invisible before because the broken delivery path never advanced
proximaEjecucion while ringing — each fix unmasked the next.

The anchor is now the newest occurrence that is not meaningfully in
the future (shared 90s imminence window): snoozeOrigen for re-snoozes,
proximaEjecucion on the watchdog path where it is still today's
just-due occurrence, ultimaEjecucionGestionada on the native-fire path
where the sync recorded the ringing occurrence, then now. ServicioAlarmas
exposes ahora() so the anchor uses the same injectable clock as the
rest of the scheduling math. Test fixtures that snoozed half an hour
before the ring — a state the ringing screen can never be in, since it
is posponerAlarma's only production caller — now move the clock to
ring time, preserving their original expectations.
2026-07-12 23:17:26 +02:00
ShanaiaBot 597c98d0e7 chore: bump version to 0.1.99+100 [ci skip] 2026-07-12 23:07:11 +02:00
FreeTLab 8741cdba5f fix(alarm): honor Dart's next-occurrence verdict on fresh schedule calls
Build & Deploy PluriWave / Análisis de código (push) Successful in 39s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m32s
The system ran two independent next-occurrence engines: Dart computes
proximaProgramable (what the UI shows) and sends it as triggerAtMillis,
but the native scheduleAlarm discarded it and recomputed from
hour/minute/weekdays. Two engines over the same data WILL diverge —
observed on-device: Dart said "today 22:48", the native weekday scan
armed next Friday, and the alarm silently never rang at its hour while
snooze (which bypasses recomputation and obeys a timestamp) always
worked. That asymmetry was the user-visible "saving an alarm breaks,
snoozing works" split.

Fresh channel calls now arm exactly the trigger Dart sent whenever it
is in the future or within the shared 90s imminence window; the native
recompute remains as the fallback for stale triggers and for
autonomous re-arms with no fresh Dart data (onAlarmFired's next
occurrence, boot/persisted reschedules). Snooze preservation is
untouched: a live native snooze still short-circuits through the
compute path. Also logs the weekdays/trigger/lastHandled payload on
every schedule call so day-convention divergences are diagnosable
from logcat.
2026-07-12 23:05:56 +02:00
ShanaiaBot 3fd5080cd1 chore: bump version to 0.1.98+99 [ci skip] 2026-07-12 12:37:23 +02:00
FreeTLab 41b95fed44 docs(openspec): archive native-alarm-ring and update the native-alarms spec
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m45s
Close the SDD cycle for the ring architecture replacement: verified
with one critical (channel silence by omission) fixed and re-checked
before archive, delta merged into the main native-alarms spec (2
requirements removed, 5 added), artifacts archived byte-for-byte.
Phase 3 on-device QA (9 items) remains the mandatory human gate.
2026-07-12 12:36:22 +02:00
ShanaiaBot a788eabfcb chore: bump version to 0.1.97+98 [ci skip] 2026-07-12 12:21:42 +02:00
FreeTLab 6f07e27905 fix(alarm): silence the fire channel explicitly instead of by omission
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m51s
Omitting setSound on a NotificationChannel leaves the platform DEFAULT
notification sound active — omission is not silence. The v3 channel
now calls setSound(null, null) exactly like the pre-notice channel
does, so the native STREAM_ALARM player stays the ring's only audible
source. Caught by verification against design D4 before any build.
2026-07-12 12:20:42 +02:00
ShanaiaBot 8a4a8bd5d7 chore: bump version to 0.1.96+97 [ci skip] 2026-07-12 12:03:57 +02:00
FreeTLab 884567beaa feat(alarm): native-only ring with DeskClock fade curve and silent channel
Build & Deploy PluriWave / Análisis de código (push) Successful in 42s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m1s
PluriWaveAlarmService becomes the sole ring-audio owner for the whole
ring (WU2 of 2, completes the split started at bd7f883): the linear
step ramp (initialVolume/startFadeIn, 250ms steps) is replaced by a
DeskClock-style exponential dB curve (computeFadeVolume: gainDb =
fraction*40-40, curve = 10^(gainDb/20)) on a 50ms Handler loop anchored
at ring start, not audio start, so all three fallback sources (station,
fallback station, bundled WAV) share one clock and a source that joins
mid-fade enters at the elapsed level instead of restarting from
silence. Each source also recomputes and applies the curve immediately
before start() to stay pop-free through prepareAsync's variable
buffering delay.

The STREAM_MUSIC device-volume override/restore is replaced by manual
AUDIOFOCUS_GAIN_TRANSIENT request/abandon on STREAM_ALARM (no-op focus
listener, requested once per ring in startAudio, abandoned
unconditionally in stopAlarm's full-teardown branch): the service never
calls setStreamVolume on any stream. The fire notification channel
migrates pluriwave_alarm_fire_v2 -> pluriwave_alarm_fire_v3, now silent
(no setSound; native MediaPlayer is the only audible source) while
keeping vibration and IMPORTANCE_HIGH for the full-screen intent; the
migration guard folds in a third delete for the v2 id alongside the two
pre-existing legacy ids, guarded by a renamed channels_migrated_v3 flag
so it still runs exactly once.

Since the Dart ringing screen (WU1) no longer calls confirmFlutterAudio,
overrideMediaVolumeForRing or restoreMediaVolume, their native surface
is now dead: deletes the flutterOwnsRing handoff flag and both its
backstop call sites in PluriWaveAlarmService, and the three
MethodChannel handlers plus their backing methods and companion state
in MainActivity.
2026-07-12 12:02:45 +02:00
ShanaiaBot 5bd861d7fb chore: bump version to 0.1.95+96 [ci skip] 2026-07-12 11:37:53 +02:00
FreeTLab bd7f883118 refactor(alarm): make the ringing screen pure UI over a reduced native port
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m40s
PantallaAlarmaSonando no longer owns any audio orchestration (fallback
player, dB ramp, native handoff confirm, media-volume override/restore):
it only calls EstadoAlarmas.finalizarEjecucion/posponerAlarma from
Stop/Snooze/back, keeping the single-exit guard, PopScope back=Stop and
dismiss semantics intact. The status line now reads directly from the
alarm's static config (station name or a neutral label) instead of a
live playback/handoff state.

PuertoAlarmasAndroid drops confirmarAudioFlutter,
forzarVolumenMediaParaAlarma and restaurarVolumenMedia, and app.dart no
longer pre-starts a station before pushing the ring screen. This is the
Dart half of moving to a single native ring-audio owner (WU1 of 2); the
Kotlin service rebuild lands next and keeps this intermediate state
shippable with no double audio.
2026-07-12 11:36:31 +02:00
ShanaiaBot 836fb44adb chore: bump version to 0.1.94+95 [ci skip] 2026-07-12 00:36:40 +02:00
FreeTLab 2e64740b26 fix(alarm): anchor the fade at alarm time and defer the override to first audio
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m41s
On-device logcat from the latest test showed two defects the previous
design created. The fade-in was gated on the station reaching
`reproduciendo`, and the stream took 18.7 seconds to buffer: the ring
sat frozen at 5% the whole time and the configured fade seconds only
started counting afterwards. And the stream override was raised during
pre-start, so the ExoPlayer AudioTrack spin-up — which runs at gain 1.0
for an instant before the player gain lands — blasted at the configured
ring level, heard as "starts directly at the alarm volume".

The ramp is now anchored at alarm time: it starts when the screen
starts, buffering just joins it at the elapsed level, and the fade
duration means seconds-from-alarm. _iniciarFadeIn is single-start so
the handoff confirmation and fallback paths can no longer restart an
in-progress ramp from 5%. The stream override moved from the app-side
pre-start into the screen and is raised only when audio is actually
about to flow (first `reproduciendo`, the already-playing branch, or
right before the fallback WAV plays), so track spin-up happens under
the user's original low volume and the blast is physically impossible.
Exit teardown restores the device stream before resetting the player
gain, removing the brief exit blip seen in the capture.
2026-07-12 00:35:29 +02:00
ShanaiaBot f73a12ad48 chore: bump version to 0.1.93+94 [ci skip] 2026-07-12 00:05:54 +02:00
FreeTLab 86225cbc68 fix(alarm): scope native stop to the ringing id and intercept system back
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m43s
Two exit-path holes found by adversarial review before the next build:

PluriWaveAlarmService.stopAlarm() never compared the requested id to
activeAlarmId, so any stop request for a DIFFERENT alarm tore down
whichever ring was active: with two alarms firing close together, the
second one's routine hide-notification call (via dismissAlarmNotification
-> ACTION_STOP) killed the first alarm mid-ring and prematurely restored
the device volume override. A mismatched id now only cancels that id's
notification and returns; null keeps full-teardown semantics for
internal/onDestroy callers.

The ringing screen never intercepted the system back gesture: a plain
route pop ran only dispose(), leaving the shared radio player ringing
with no alarm UI left anywhere to stop it. Back now routes through
PopScope into the same _detener() flow as the Stop button, guarded by a
single-exit flag so a back-press racing a button tap cannot run the
teardown twice and pop the route underneath.

Also resets the shared handler gain to 1.0 on ring exit: the fade-in
mutates the radio player's persistent volume, and exiting mid-ramp used
to leave every later radio play at the partial ramp level.
2026-07-12 00:04:53 +02:00
ShanaiaBot e84a41acd4 chore: bump version to 0.1.92+93 [ci skip] 2026-07-11 23:33:15 +02:00
FreeTLab 1b1b692f04 fix(alarm): apply the configured fraction to the ring volume override
Build & Deploy PluriWave / Análisis de código (push) Successful in 37s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m45s
overrideMediaVolumeForRing parsed and logged the fraction argument but
called the no-arg override, which always forced STREAM_MUSIC to the
device maximum. On-device logcat confirmed it: the Dart side sent
fraction=0.5 yet the stream was set to index=30 of 30. Combined with
the player now ramping to its full range, the ring peaked at 100% of
the device maximum instead of the configured 50%.

The override now sets the stream to round(max * fraction), clamped to
at least 1 so rounding can never mute the ring. With fraction=0.5 the
stream caps at half the device maximum and the player ramps up to that
cap, so the ring peaks at the configured level and the opening buffer
click drops to the configured fraction rather than full scale.
2026-07-11 23:32:05 +02:00
ShanaiaBot 5f65d068a8 chore: bump version to 0.1.91+92 [ci skip] 2026-07-11 23:03:13 +02:00
FreeTLab 3ebb41aa9d fix(alarm): arm a just-passed occurrence instead of skipping it a day
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m42s
The native next-occurrence recompute required the trigger to be
strictly in the future, while the Dart side keeps an occurrence whose
trigger passed within a 90s tolerance. When the periodic resync
re-armed an alarm microseconds after its trigger (app foregrounded,
the 60s tick straddling the trigger instant), computeNextTriggerMillis
recomputed the next weekday/daily occurrence as tomorrow and, through
the shared FLAG_UPDATE_CURRENT fire PendingIntent, replaced the
in-flight fire before AlarmManager delivered it. The alarm never rang
until the screen was turned on and the Dart watchdog caught it late.

computeNextTriggerMillis now mirrors Dart's toleranciaDisparoInminente:
base is lowered by a 90s grace window so a just-passed occurrence is
armed (and delivered ~immediately) rather than pushed to the next day.
The handledFloor (lastHandledAtMillis + 60s) stays a hard lower bound,
so an already-fired occurrence can never be re-selected — no
double-fire. Dart contract tests lock the boundary the native constant
must track. Native verification is on-device (no JVM test harness).
2026-07-11 23:02:14 +02:00
ShanaiaBot 812922d7f1 chore: bump version to 0.1.90+91 [ci skip] 2026-07-11 22:49:56 +02:00
FreeTLab 5291221fc8 fix(alarm): cap the ring at the configured volume instead of the device max
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m48s
The ring-scoped media override forced STREAM_MUSIC to the hardware
maximum, so the alarm's configured percentage was applied on top of a
maxed speaker: "50%" meant 50% of the phone's absolute maximum and
the fade rode against that ceiling, far louder than the device-
relative level users were used to. On-device logcat also showed the
just_audio player emitting one buffer at volume 1.0 before the 5%
pre-start took effect, a full-scale click on the maxed stream.

The stream is now capped at the alarm's configured volume (still
independent of the device's own level, so it rings at device-volume
0), and the player ramps from ~5% up to its full range under that
cap. Perceived peak is the configured fraction of the device maximum,
reached gradually; the opening click drops to the configured fraction
instead of full scale.
2026-07-11 22:48:56 +02:00
ShanaiaBot 2c28f1696a chore: bump version to 0.1.89+90 [ci skip] 2026-07-11 22:27:25 +02:00
FreeTLab b294b287c7 fix(alarm): stop a duplicate fire event from killing the active ring
Build & Deploy PluriWave / Análisis de código (push) Successful in 40s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m31s
The live eventosAlarma stream and the one-shot obtenerEventoInicial()
both read the same native fire event on cold start, so the same alarm
id can reach _mostrarAlarmaSonando twice within the same tick. The
second, duplicate delivery correctly detected an alarm was already
active and hit the "ignored" branch — but that branch unconditionally
called ocultarNotificacionAlarma, whose native handler
(dismissAlarmNotification) unconditionally stops
PluriWaveAlarmService for that id.

Confirmed via on-device logcat: the duplicate's stop landed ~180ms
after the ring-scoped media-volume override was captured and ~2.3s
before the real native-to-Flutter handoff, so flutterOwnsRing was
still false and the teardown backstop restored the device's original
volume immediately. The Flutter/radio player kept ringing regardless
(it starts independently of the native service), now anchored to
whatever volume the device happened to be at — explaining both
"ignores the configured ramp" and "plays at the device's own volume."

The ignored branch now only hides the notification when the duplicate
carries a genuinely different alarm id than the one already ringing;
a duplicate of the SAME ring's own event is now a pure no-op.
2026-07-11 22:26:13 +02:00
ShanaiaBot b7af1064cc chore: bump version to 0.1.88+89 [ci skip] 2026-07-11 17:35:37 +02:00
FreeTLab c65497e58a docs(openspec): archive persistence-corruption-guard and promote its spec
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m49s
Close the SDD cycle for the data-loss shielding change: verified pass
with warnings (0 critical, 10/10 scenarios with named tests, W1 fixed
post-verify), three stacked work units shipped plus the path-resolution
fix. The persistence-resilience capability spec is promoted to
openspec/specs/.
2026-07-11 17:34:36 +02:00
ShanaiaBot 762e740c89 chore: bump version to 0.1.87+88 [ci skip] 2026-07-11 17:21:09 +02:00
FreeTLab 48e74e6bfe fix(radio): treat custom-station path resolution as part of the IO surface
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
_cargarEmisorasCustom resolved the file path outside the IO guard, so
a throw from the resolver escaped into _init()'s Future.wait and took
the sibling loads (populares, favoritos, grupos) down with it — a gap
the old catch-all used to cover. Path resolution now gets the same
IO-fail treatment as an unreadable file: degraded flag, logged skip,
siblings unaffected.
2026-07-11 17:19:44 +02:00
ShanaiaBot d824ef5c21 chore: bump version to 0.1.86+87 [ci skip] 2026-07-11 13:01:16 +02:00
FreeTLab 45b7fc8741 fix(eq): keep valid presets when stored maps are partially corrupt
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m39s
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Convert the 4 EQ persistence readers (device presets, matrix presets,
device names, per-station presets) to per-entry tolerant parsing via
the shared persistencia_tolerante helper, so one corrupt entry no
longer discards every sibling preset. The principal-preset reader
gains diagnostic logging on its existing fallback path. No degraded
flag or quarantine here (unlike alarms/stations) since EQ writes are
explicit-only and presets are trivially re-creatable.
2026-07-11 13:00:15 +02:00
ShanaiaBot 2255c18ce8 chore: bump version to 0.1.85+86 [ci skip] 2026-07-11 12:46:49 +02:00
FreeTLab 13ad736917 fix(radio): quarantine corrupt custom-station files instead of silently emptying them
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m39s
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
A single malformed custom-station entry (missing uuid/url) used to wipe
the ENTIRE list on next load, and an unparseable file was treated the
same as an unreadable one -- both destroyed the user's saved stations
with no way to recover the original bytes.

Custom stations now parse per-entry via the shared persistencia_tolerante
helper (survivors kept, bad entries skipped+logged); a file that reads
but fails to decode is quarantined into a `.corrupt` sidecar instead of
being dropped, clearing the live path so the next add/remove starts
fresh. A file that cannot be READ at the OS level is left untouched and
a _customDegradado flag suppresses writes for the session -- unlike
alarms, this suppression is intentionally not lifted by an explicit
add/remove, since the file may still be intact on disk.
2026-07-11 12:45:52 +02:00
ShanaiaBot a34182fdaf chore: bump version to 0.1.84+85 [ci skip] 2026-07-11 12:28:50 +02:00
FreeTLab 65c1ac2085 fix(alarm): stop corrupt entries and unreadable payloads from wiping saved alarms
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m38s
A single malformed alarm entry (bad/missing id, wrong type) used to
discard the ENTIRE persisted list on next load, and a fully unparseable
payload let the periodic recalculation silently overwrite it with an
empty one -- both destroyed valid alarms with no user action.

Adds a shared per-entry tolerant-parse helper (persistencia_tolerante.dart)
that skips and logs only the bad entry; ServicioAlarmas now normalizes its
cached raw after a partial load (no dirty-guard thrash) and sets a
degraded-read flag after a total decode failure that suppresses automatic
writes until a good read or an explicit user mutation restores authority.
2026-07-11 12:27:39 +02:00
ShanaiaBot 23ab3494a7 chore: bump version to 0.1.83+84 [ci skip] 2026-07-11 11:09:36 +02:00
FreeTLab 7eaa87b462 fix(alarm): start the fade-in when pre-started audio is already playing
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m48s
The ringing screen only confirmed the native-to-Flutter handoff from
its playback-state listener, but app.dart pre-starts the station
before pushing the screen, so `reproduciendo` could be emitted before
the listener subscribed and no further event ever arrived. That
branch only cancelled the fallback timer: the gated fade-in never
started and the native alarm player was never told to stop, so the
alarm blared at the alarm-stream volume with no 5%-to-target ramp.
Previously this was a timing race the stream usually lost; gating the
ramp on the confirmation made the failure deterministic.

The already-playing branch now confirms the handoff explicitly
(idempotent with the listener), and the ramp re-imposes its 5% start
volume immediately instead of waiting for the first periodic tick.
Adds the regression test mounting in the real pre-started path.
2026-07-11 11:08:33 +02:00
ShanaiaBot f6ea4e64b9 chore: bump version to 0.1.82+83 [ci skip] 2026-07-11 10:33:15 +02:00
FreeTLab efbf289f6b docs(openspec): archive alarm-volume-ramp-restore and promote native-alarms spec
Build & Deploy PluriWave / Análisis de código (push) Successful in 39s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m48s
Close the SDD cycle: verified pass with warnings (0 critical), slice 1
cancelled with SDK evidence, slices 2-3 shipped, post-verify dispose
fix landed. The native-alarms capability spec is promoted to
openspec/specs/ with the corrected FGS requirement. Phase 5 on-device
QA remains the pending human gate.
2026-07-11 10:32:07 +02:00
ShanaiaBot 0804a612ec chore: bump version to 0.1.81+82 [ci skip] 2026-07-11 10:17:46 +02:00
FreeTLab 79f6f8ef38 fix(alarm): capture alarm state before dispose so its volume restore works
Build & Deploy PluriWave / Análisis de código (push) Successful in 43s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m52s
_restaurarVolumenMediaUnaVez() read the BuildContext to reach the
alarm port, but dispose() runs after the element is defunct, so the
lookup always threw (caught and logged) and the dispose safety-net
never actually restored the media volume when it was the sole exit
path. The state is now captured once in initState and the restore
helper uses the field. Adds the missing dispose-as-sole-caller
regression test.
2026-07-11 10:16:25 +02:00
ShanaiaBot b9539223de chore: bump version to 0.1.80+81 [ci skip] 2026-07-11 09:58:08 +02:00