Commit Graph
233 Commits
Author SHA1 Message Date
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
FreeTLab 66a19525bd fix(alarm): defer the Dart fade-in until the native handoff confirms
Build & Deploy PluriWave / Análisis de código (push) Successful in 37s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m50s
The native service and the Flutter player each ran their own 5%-to-
target fade-in, and both could drive audible volume at the handoff,
producing a jump or ramp reset. The Dart ramp now starts exactly once
from the handoff-confirmation path: the player still pre-starts at 5%,
and _confirmarAudioFlutterListo() starts the ramp in a finally block
so it runs whether the native confirmation succeeds or fails — the
alarm can never stay stuck at 5% if the native side is already gone.

Work unit 3/3 of alarm-volume-ramp-restore (fade-in dedup).
2026-07-11 09:57:06 +02:00
ShanaiaBot a6e1177752 chore: bump version to 0.1.79+80 [ci skip] 2026-07-11 09:17:06 +02:00
FreeTLab acd903d9a8 feat(alarm): make the ring immune to device media volume
Build & Deploy PluriWave / Análisis de código (push) Successful in 41s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m39s
The alarm's steady-state audio runs on the Flutter media-stream
player after the native handoff, so device volume 0 silenced it
entirely. The ring now forces STREAM_MUSIC to an audible reference:
Dart requests the override before pre-starting alarm audio (fallback
WAV included), Kotlin captures the current volume once and restores
it idempotently on every exit path (dismiss, snooze, dispose), with
a native best-effort backstop in service teardown.

The backstop is handoff-aware via PluriWaveAlarmService.flutterOwnsRing:
confirmFlutterAudio marks the handoff before triggering the native
stop, so the backstop cannot restore the volume mid-ring right as the
Flutter player takes over (that would re-silence the alarm at volume
0). The flag resets at every ring start; Flutter process death after
handoff remains a documented best-effort gap.

The alarm's perceived loudness keeps ramping 5% to the configured
volume through the player as before; normal radio playback and call
ducking never touch the override.

Work unit 2/3 of alarm-volume-ramp-restore (ring volume override).
2026-07-11 09:15:37 +02:00
ShanaiaBot 251d3fd3cd chore: bump version to 0.1.78+79 [ci skip] 2026-07-11 01:23:25 +02:00
FreeTLab 43f61d7c21 docs(openspec): cancel alarm FGS slice, the alarm service type never existed
Build & Deploy PluriWave / Análisis de código (push) Successful in 34s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m43s
Apply-stage SDK verification (javap on android-34/35/36 platform jars
plus api-versions.xml) proved FOREGROUND_SERVICE_TYPE_ALARM and the
FOREGROUND_SERVICE_ALARM permission are fictional constants. The
existing mediaPlayback|systemExempted declaration is the documented
correct pattern for an alarm app holding exact-alarm permissions, so
slice 1 ships no code and root cause B is withdrawn. Spec, design,
and tasks amended with the evidence; volume-override and fade-dedup
slices proceed unaffected.
2026-07-11 01:22:18 +02:00
ShanaiaBot 39693ce995 chore: bump version to 0.1.77+78 [ci skip] 2026-07-11 01:16:08 +02:00
FreeTLab 159334f997 docs(openspec): archive bt-device-identity and promote its spec
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m44s
Move the change folder to changes/archive/2026-07-11-bt-device-identity
with the verified artifact set (verdict: pass with warnings, 0 critical,
102/102 targeted tests) and create the bt-device-identity capability
spec under openspec/specs/. Phase 7 on-device QA remains the pending
human gate before release.
2026-07-11 01:15:06 +02:00
ShanaiaBot 41b35c7f44 chore: bump version to 0.1.76+77 [ci skip] 2026-07-11 00:57:31 +02:00
FreeTLab 747738d20a docs(openspec): add SDD artifact trails for bt-device-identity and alarm-volume-ramp-restore
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m38s
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
In-progress artifact sets from the current SDD cycles: exploration,
proposal, spec, design, tasks, and verify reports as produced so far.
Also drops a leftover working copy of eq-device-disconnect-revert
whose contents were already committed under changes/archive/.
2026-07-11 00:56:22 +02:00
ShanaiaBot 0b18540935 chore: bump version to 0.1.75+76 [ci skip] 2026-07-11 00:54:46 +02:00
FreeTLab 8cca7c3daa test(devices): cover rename surviving a re-pair cycle end to end
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m42s
The rename-priority and reconnect-dedup behaviors were each tested in
isolation but never composed: connect, rename, disconnect, re-pair
with the same MAC. Adds that regression test asserting no duplicate
entry appears, the preset entry survives untouched, and the custom
name still wins after reconnection.
2026-07-11 00:53:45 +02:00
ShanaiaBot 158203fee9 chore: bump version to 0.1.74+75 [ci skip] 2026-07-11 00:26:07 +02:00
FreeTLab b17c582572 fix(devices): cache platform device names, dedupe placeholder ids, purge collided EQ entries
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m47s
Dart half of bt-device-identity. EstadoEcualizador now caches each
device's platform-reported name in memory so the settings screen
shows the device's own Bluetooth name instead of its raw id when no
custom rename exists, and skips auto-creating preset entries for the
composite-placeholder sentinel. Enabling multi-device EQ triggers the
Bluetooth permission request through the new channel contract. A
flag-guarded one-time migration purges only entries keyed by the
exact literal placeholder id from the three per-device preference
maps, since those collided entries cannot be attributed to a device.

Work unit 2/2 of bt-device-identity (Dart state + migration).
2026-07-11 00:25:00 +02:00
ShanaiaBot 224763bca3 chore: bump version to 0.1.73+74 [ci skip] 2026-07-11 00:03:51 +02:00
FreeTLab aef4e02c1f fix(devices): use real Bluetooth MAC as device identity on Android 12+
Build & Deploy PluriWave / Análisis de código (push) Successful in 46s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m53s
Without BLUETOOTH_CONNECT, Android 12+ returns the fixed placeholder
02:00:00:00:00:00 for every Bluetooth device's address, so all BT
devices collapsed onto the same equalizer identity and renames
appeared to duplicate devices after re-pairing.

Declare the permission, add a requestBluetoothConnect channel method
mirroring the existing notification-permission flow, guard the
placeholder in deviceToMap() with a colon-sanitized name-based
fallback id (replacing the dead 00:00:00:00:00:00 branch), and
re-emit the active device after the grant so already-connected
devices pick up their real MAC without a reconnect.

Work unit 1/2 of bt-device-identity (Kotlin plumbing).
2026-07-11 00:02:38 +02:00
ShanaiaBot d3763eaec5 chore: bump version to 0.1.72+73 [ci skip] 2026-07-10 23:55:15 +02:00
FreeTLab 8f7ca8059b fix(eq): resolve base-speaker preset live instead of pinning a stale copy
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
_onDispositivoCambiado() bootstrapped a device-level preset entry for
every never-seen device id, including the built-in speaker. That
persistent level-3 entry masked later global-preset edits (level 3
beats level 4 on every resolution), so disconnecting a BT device or
cold-starting without one could leave the EQ stuck on an outdated
copy instead of the current global preset.

The base speaker is now excluded from the first-seen bootstrap:
disconnect and cold start always resolve through the live hierarchy.
BT/wired/USB devices keep their bootstrap behavior unchanged.
2026-07-10 23:54:01 +02:00
ShanaiaBot bfa95a1e57 chore: bump version to 0.1.71+72 [ci skip] 2026-07-10 18:53:22 +02:00
FreeTLab 0ab63731d0 fix(eq): re-apply equalizer when the native audio session rotates
Build & Deploy PluriWave / Análisis de código (push) Successful in 41s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m33s
ExoPlayer assigns a new audio session id after transient audio-focus
interruptions (navigation prompts, radar warnings), leaving the
AndroidEqualizer attached to the dead session so playback resumed
without equalization until the next station switch. The session-id
listener now detects genuine rotations through a dedicated guard and
re-activates the equalizer with the current preset, gated on EQ
availability to stay clear of player teardown/rebuild.
2026-07-10 18:51:45 +02:00
ShanaiaBot a31dc07318 chore: bump version to 0.1.70+71 [ci skip] 2026-07-04 12:43:21 +02:00
FreeTLab bccc5c48b8 docs(openspec): add SDD artifact trail for recent alarm and EQ changes
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
Persist the exploration, proposal, spec, design, tasks, and
verify/archive reports produced during the multi-device EQ,
alarm-countdown, and notification-visual-polish SDD cycles.
2026-07-04 12:42:11 +02:00
ShanaiaBot e5b6d8acb3 chore: bump version to 0.1.69+70 [ci skip] 2026-07-02 18:54:39 +02:00
FreeTLab 8f2bf2bdd6 feat(notifications): add branded monochrome icon and color to all notifications
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m28s
Replace generic system icons (info bubble, stock alarm clock) with a
custom equalizer-bars vector drawable across all 4 notification
builders: pre-notice, snooze countdown, ringing alarm, and the audio
player. Apply the app's cyan brand color to the 3 alarm notifications
that previously had none. Audio notification now explicitly declares
its icon instead of falling back to the full-color launcher icon,
which Android was auto-silhouetting into an illegible status-bar
blob.
2026-07-02 18:53:33 +02:00
ShanaiaBot c78af4b1e8 chore: bump version to 0.1.68+69 [ci skip] 2026-07-02 15:23:08 +02:00
Javier Bautista Fernández 28b663bbe7 fix(alarm): recalculate every alarm on each mutation, not just the touched one
Build & Deploy PluriWave / Análisis de código (push) Successful in 37s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m33s
guardarAlarma only recomputed proximaEjecucion for the alarm being
saved; every other alarm kept whatever snapshot the last periodic
recalculation left, which can be stale or already past-due. Since
EstadoAlarmas.proximaAlarma just sorts by proximaProgramable, a stale
sibling could wrongly outrank a freshly activated/created/edited
alarm in the "Próxima alarma" panel until the next 1-minute tick.

Extended the same full-list recalculation guardarVacaciones already
did to guardarAlarma, eliminarAlarma, completarEjecucion,
sincronizarEjecucionesNativas, saltarProxima and
posponerEjecucionHasta, via a shared _recalcularLista helper.
2026-07-02 15:21:58 +02:00
ShanaiaBot a8a2db4b64 chore: bump version to 0.1.67+68 [ci skip] 2026-07-02 10:53:12 +02:00
Javier Bautista Fernández 7cfde24811 fix(alarm): avoid ClassCastException casting snooze millis to Long
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m29s
Flutter's StandardMethodCodec encodes Dart ints that fit in 32 bits as
Java Integer, not Long. scheduleAlarm sends preNoticeAtMillis=0 when
rescheduling a snooze, which crashed the unchecked argument<Long>()
cast. Read all millis args as Number and convert with toLong().
2026-07-02 10:52:07 +02:00
ShanaiaBot da9d32849c chore: bump version to 0.1.66+67 [ci skip] 2026-07-01 00:24:18 +02:00
FreeTLab 6acbd7ca93 fix(alarm): handle snooze reschedule failures instead of silently dropping them
Build & Deploy PluriWave / Análisis de código (push) Successful in 47s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m1s
posponerAlarma() and posponerProximaDesdePreaviso() called the native
scheduler with no error handling, unlike guardarAlarma(). When the
native call failed (e.g. revoked exact-alarm permission), the
exception escaped before notifyListeners() ran, leaving the alarm
list stuck on stale data with no real alarm scheduled and no snooze
countdown notification.

Both methods now mirror guardarAlarma()'s pattern: permission
pre-check, try/catch into _error, and an unconditional
notifyListeners() so the UI always reflects the outcome. Failures
surface via SnackBar in the ringing screen and in app.dart's
postpone-next handler.
2026-07-01 00:22:58 +02:00
ShanaiaBot cc98f3f331 chore: bump version to 0.1.65+66 [ci skip] 2026-06-30 22:11:58 +02:00
FreeTLab 5877c2a4ee feat(alarm): add true per-minute live countdown to pre-notice
Build & Deploy PluriWave / Análisis de código (push) Successful in 41s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m34s
Mirror the shipped snooze-countdown chain for the 30-min pre-notice
notification: re-arm ACTION_PRE_NOTICE at each minute boundary via
slot 9, self-stop at remaining<=1, self-heal from wall clock on
missed ticks. Wire cancellation at all 5 sites (cancelAlarm,
scheduleSpec no-trigger branch, snooze-transition branch,
ACTION_SKIP_NEXT, ACTION_POSTPONE_NEXT) using AlarmScheduler's own
requestCode formula to keep PendingIntent identity consistent.
2026-06-30 22:10:41 +02:00
ShanaiaBot f5cf261f12 chore: bump version to 0.1.64+65 [ci skip] 2026-06-30 15:44:24 +02:00
Javier Bautista Fernández ffd09a2179 i18n(alarm): localize all native notification, channel and chooser texts
Build & Deploy PluriWave / Análisis de código (push) Successful in 40s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m33s
Centralize every native-side user-facing string in a single
AlarmNotificationStrings store written by Flutter via a new
setNotificationStrings MethodChannel whenever the app locale changes,
and read at notification/channel build time (with English fallbacks)
even when the engine is dead. This replaces the hardcoded Spanish text
in the ringing notification ("Alarma PluriWave", "Posponer", "Detener"),
the pre-notice notification ("Posponer", "Omitir esta vez"), both
notification channels (names + descriptions) and the file-action
choosers ("Abrir carpeta", "Abrir grabación").

The per-alarm preNoticeTemplate/snoozeCountdown template+label args are
dropped from scheduleAlarm and the persisted spec and folded into the
shared store, so a locale change now also relocalizes already-scheduled
alarms. Channels are re-created on each use so their name/description
refresh after a language switch.

Adds alarmRingingNotificationTitle, alarmFire/PreNoticeChannelName,
alarmFire/PreNoticeChannelDescription and openFolder/openRecording
chooser keys across all 13 locales (reusing snoozeAction, stopAlarmAction,
skipNextAction, snoozeAgainAction). Rewrites the template test around
setNotificationStrings. Kotlin is static-reviewed only; no Android build
environment available here.
2026-06-30 15:41:29 +02:00
Javier Bautista Fernández 481944815f fix(alarm): unfreeze snooze modal and add per-minute snooze countdown
The snooze button was fire-and-forget without error handling: if
posponerAlarma threw (e.g. native scheduleAlarm returns false on a
device without exact-alarm permission), _dismissScreen never ran. The
stuck modal also kept _alarmaSonandoActiva true, which made the next
ring get ignored. _posponer/_detener now dismiss in a finally and stop
audio defensively.

Add a native, AlarmManager-driven snooze countdown notification that
re-posts every minute ("Rings in N min", 3->2->1) while the engine is
dead. scheduleSpec drives scheduleSnoozeCountdown for snoozes (instead
of the 30-min pre-notice). Localized text and button labels travel
Dart->Kotlin as {minutes} templates, same pattern as preNoticeTemplate.

Notification actions: "snooze again" (snoozeAgain, anchored to now)
reports back via the existing snoozed event; "stop" (cancelSnooze)
records a handled occurrence for cold-start reconciliation and emits a
new snoozeCancelled event handled in EstadoAlarmas.

Adds snoozeCountdown/snoozeAgainAction keys across all 13 locales and a
test for the snoozeCancelled event. Kotlin changes are static-reviewed
only; no Android build environment available here.
2026-06-30 15:27:05 +02:00
ShanaiaBot 89ff6a3912 chore: bump version to 0.1.63+64 [ci skip] 2026-06-28 11:56:21 +02:00
FreeTLab 4ffd73d136 fix(alarm): localize pre-notice countdown and fix snooze dismiss
Build & Deploy PluriWave / Análisis de código (push) Successful in 39s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m33s
Replace hardcoded Spanish pre-notice text with computed remaining
minutes using l10n template passed via MethodChannel. Fix snooze
dismiss in dead-app state with canPop guard and SystemNavigator.pop
fallback.
2026-06-28 11:55:15 +02:00
FreeTLab 58922de6fc fix(eq): seed device ID at startup and add device management UI
Fix multi-device EQ auto-switching by calling obtenerDispositivoActual()
during cargarPersistido() to seed the initial device ID. Add device
management modal with rename support, EQ preset editing, and connection
status indicator. Translate device UI keys to all 13 locales.
2026-06-28 11:55:15 +02:00
ShanaiaBot 71978de68f chore: bump version to 0.1.62+63 [ci skip] 2026-06-27 12:16:24 +02:00
FreeTLab d1d4afb88f i18n(eq): translate advanced EQ keys to all 11 remaining locales
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m25s
Add advancedEq* translations for ar, bn, de, fr, hi, id, it, ja, pt,
ru, zh — matching register and style conventions of each locale.
2026-06-27 11:38:45 +02:00
FreeTLab 4632d53eb8 feat(eq): add per-device equalizer with 4-level preset resolution
Introduce multi-device EQ support allowing each audio output device
(built-in speaker, wired, USB, individual Bluetooth by MAC) to have
its own equalizer preset, combined with existing per-station presets
for a full station×device matrix.

- Add DispositivoAudio model and ServicioDispositivoAudio interface
- Add Android platform channel (AudioDeviceCallback) for device detection
- Add iOS AudioDevicesPlugin (AVAudioSession route tracking)
- Extend ServicioEcualizador with device and matrix persistence keys
- Implement 4-level resolution: matrix > station > device > global
- Add advanced EQ settings section with feature toggle (off by default)
- Extend export/import to v3 with backward compatibility
- 184 tests passing, zero analyzer issues
2026-06-27 11:33:53 +02:00
ShanaiaBot 8f42e67b48 chore: bump version to 0.1.61+62 [ci skip] 2026-06-26 23:05:20 +02:00
FreeTLab f7753c8402 Merge branch 'feat/s6-quality-gates' into main
Build & Deploy PluriWave / Análisis de código (push) Successful in 1m10s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 3m29s
2026-06-12 00:12:43 +02:00
FreeTLab 8a032e6e62 feat(quality): harden lint rules and add quality-gate tests 2026-06-12 00:05:06 +02:00