Commit Graph
48 Commits
Author SHA1 Message Date
Javier Bautista Fernández 2a5030431b fix(android): break literal /* in KDoc that unclosed the comment
Build & Deploy PluriWave / Análisis de código (push) Successful in 24s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m15s
Kotlin nests block comments, so the literal `audio/*` text inside a
KDoc comment opened a phantom nested comment. The closing */ two
lines later closed that nested one instead, leaving the real KDoc
open for the rest of the file and breaking release compilation.
2026-07-20 10:22:10 +02:00
FreeTLab 352eb9fc37 feat(auto): real metadata, quality sort and name buckets for local music [size:exception]
Local tracks now show embedded title/artist/album art (via native
MediaMetadataRetriever, cached through the existing FileProvider)
instead of the raw filename, falling back gracefully when a file
has no usable tags. Adds two navigable entry points per folder: sort
by audio quality (bitrate, capped at 150 tracks per folder to bound
worst-case latency) and alphabetical name buckets -- the closest
realistic form of "filtering" given Android Auto has no text-search
UI in this integration.

Metadata resolves only for the page actually being browsed (same
slice-cheap-then-map discipline as the paging change), backed by a
flat 256-entry LRU session cache that survives across pages. No new
permission, no new pub dependency, no l10n changes (car-tree labels
stay hardcoded Spanish, matching every existing label in the tree).
2026-07-19 23:52:08 +02:00
FreeTLab 6ae7e378c4 feat(auto): browse and play local music folders in Android Auto [size:exception]
Phase 1: pick a device folder via SAF (persisted grant, no new
permission), browse its nested subfolders/tracks as a 5th Android
Auto root folder (hidden until configured), and play tracks through
the existing pipeline (EQ, art rotation, cold-start-safe source).
No metadata/sort/filter/shuffle yet -- filename is the title, generic
rotating art is the placeholder; deferred to a follow-up phase.

Adds a new pluriwave/file_actions native method (listAudioChildren)
and an onActivityResult override in MainActivity for the SAF folder
picker -- both static-review-only, no Android build available here.
2026-07-19 20:30:50 +02:00
FreeTLab c193650cc4 fix(auto): fall back to brand art and surface quality on dead/missing favicons
Android Auto no longer copies the launcher icon as placeholder art; it
rotates through the same 4 on-brand station_art assets the phone UI
already uses, keyed by the same per-station hash for visual parity.
Malformed or unusable favicon URLs (including a Dart Uri quirk where
'http://' reports hasAuthority=true with an empty host) now fail the
validity gate instead of being handed to the OS media browser as-is.
Browsable items also show codec/bitrate as a subtitle when known.
2026-07-19 13:06:18 +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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 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
FreeTLab f3e9487215 feat(alarms): native reliability fixes and end-to-end snooze
- Use mediaPlayback|systemExempted FGS type with FOREGROUND_SERVICE_SYSTEM_EXEMPTED so alarms fire on Android 14+ (FOREGROUND_SERVICE_ALARM does not exist in the SDK)
- Deduplicate fire notifications: the foreground service FSI notification is the single owner; receiver path removed
- Notification channel v2 with alarm sound URI and USAGE_ALARM attributes, one-time guarded migration from legacy channels
- Pass fallback station through the MethodChannel (NativeAlarmSpec schemaVersion 3) with a three-stage audio chain: primary -> fallback station -> bundled WAV
- Native fade-in volume ramp honoring fadeInSegundos when the app is killed
- Request battery-optimization exemption once, tracked with a persisted asked-once flag
- Fix snooze end-to-end: native ACTION_SNOOZE now reports back to Flutter (snoozed event + cold-start sync), snooze anchor unified to occurrence+minutes on both sides, periodic recalc no longer erases an active snooze
- Add snooze buttons (3/5/10/custom) to the ringing screen with shared audio teardown
- Redesign ringing screen on PluriWaveScaffold with reduced-motion-aware entry animation (new PluriAnimate helper)
- Alarm editor: live next-trigger preview, searchable station pickers (primary and fallback), configurable snooze duration, volume floor down to 0
- New alarm strings localized across all 13 locales
- New unit/widget tests for the snooze flow, alarm bridge payloads, ringing screen and editor (77 tests green)
- SDD artifacts for the app-quality-and-native-alarms change (explore, proposal, spec, design, tasks, apply progress)
2026-06-11 15:33:30 +02:00
Javier Bautista Fernández ffe1c41458 eliminados los snooze 2026-06-02 09:21:43 +02:00
Javier Bautista Fernández 028e2d69b1 fix(alarms): harden native alarm lifecycle
Build & Deploy PluriWave / Build APK + AAB release (push) Has been cancelled
Build & Deploy PluriWave / Análisis de código (push) Has been cancelled
2026-05-29 13:13:39 +02:00
Javier Bautista Fernández 9a9ef95b07 Merge remote-tracking branch 'origin/main'
Build & Deploy Pluriwave / Análisis de código (push) Failing after 13m6s
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 14m58s
# Conflicts:
#	android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmScheduler.kt
2026-05-28 12:31:12 +02:00
Javier Bautista Fernández 659e6da189 fix(alarms): harden native playback and pre-notice actions 2026-05-28 12:03:58 +02:00
FreeTLab 7dceed5dae fix(ci): load release signing from key properties
Build & Deploy Pluriwave / Análisis de código (push) Successful in 22s
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 1m32s
2026-05-25 21:50:03 +02:00
FreeTLab e447816d3f Merge branch 'main' of https://git.freetimelab.es/FreeTLab/pluriwave
Build & Deploy Pluriwave / Análisis de código (push) Successful in 23s
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 1m38s
2026-05-25 21:37:42 +02:00
FreeTLab c189078c26 Corrección de publicación 2026-05-25 21:37:40 +02:00
FreeTLab 42dd64635c feat(pluriwave): añadir firma release con keystore pluriwave-upload para Google Play
Build & Deploy Pluriwave / Análisis de código (push) Failing after 3s
Build & Deploy Pluriwave / Build APK + AAB release (push) Has been skipped
2026-05-25 20:47:19 +02:00
FreeTLab 896349ad5f feat(app): add onboarding and harden alarms
Build & Deploy Pluriwave / Análisis de código (push) Successful in 21s
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 1m6s
2026-05-23 01:22:49 +02:00
FreeTLab 3ab138a4fa feat(alarms): add native ringing service
Build & Deploy Pluriwave / Análisis de código (push) Successful in 26s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 2m8s
2026-05-22 20:02:27 +02:00
FreeTLab a976b8e797 fix(alarms): fallback native scheduling
Build & Deploy Pluriwave / Análisis de código (push) Successful in 27s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 2m28s
2026-05-22 19:23:57 +02:00
FreeTLab ee09224c13 fix(android): import activity not found exception
Build & Deploy Pluriwave / Análisis de código (push) Successful in 26s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 2m10s
2026-05-22 18:54:34 +02:00
FreeTLab a48dd6ddf9 fix(alarms): refresh next execution reliably
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 1m0s
Build & Deploy Pluriwave / Análisis de código (push) Successful in 23s
2026-05-22 18:42:11 +02:00
FreeTLab 809255bd43 fix(recordings): open last file on android
Build & Deploy Pluriwave / Análisis de código (push) Successful in 23s
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 1m2s
2026-05-22 18:30:49 +02:00
FreeTLab 9ad58898e0 fix(recordings): open folder with android picker
Build & Deploy Pluriwave / Análisis de código (push) Successful in 24s
Build & Deploy Pluriwave / Build APK + AAB release (push) Failing after 1m0s
2026-05-22 18:24:25 +02:00
FreeTLab 4ae93182fa fix(alarm): add due alarm watchdog
Build & Deploy Pluriwave / Análisis de código (push) Successful in 14s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 4m14s
2026-05-22 01:26:36 +02:00
FreeTLab eeadcc1cc6 fix(alarm): improve firing and preferred station
Build & Deploy Pluriwave / Análisis de código (push) Successful in 15s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 4m15s
2026-05-22 01:06:02 +02:00
FreeTLab a3a648c633 feat(alarm): complete musical alarm flows
Build & Deploy Pluriwave / Análisis de código (push) Successful in 15s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 4m21s
2026-05-22 00:40:01 +02:00
FreeTLab fb808ebb60 feat(alarm): add musical alarm foundation
Build & Deploy Pluriwave / Análisis de código (push) Successful in 14s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 2m45s
2026-05-21 23:47:06 +02:00
FreeTLab a6a91af402 feat(player): add radio recording and real waveform
Build & Deploy Pluriwave / Análisis de código (push) Successful in 12s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 1m27s
2026-05-21 21:17:59 +02:00
FreeTLab 7fcd0f544e feat(radio): add nearby discovery and paged search
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 1m34s
Build & Deploy Pluriwave / Análisis de código (push) Successful in 11s
2026-05-20 23:22:23 +02:00
ShanaiaBot 44849986d2 fix(reproduccion): robustez HTTP cleartext, errores ExoPlayer y certificados SSL
Flutter CI/CD — PluriWave / Test + Build (pull_request) Has been cancelled
**Fix 1 — HTTP cleartext (streams sin HTTPS):**
- Añadir android/app/src/main/res/xml/network_security_config.xml con
  cleartextTrafficPermitted=true para permitir streams de radio HTTP
- Referenciar en AndroidManifest.xml con android:networkSecurityConfig
- Resuelve: 'Cleartext HTTP traffic to [host] not permitted' en ExoPlayer
- Radio Paradise (Dance Wave, HTTP) y otras radios HTTP funcionan ahora

**Fix 2 — Gestión de error TYPE_SOURCE y todos los PlaybackException:**
- Añadir listener en playbackEventStream.onError en PluriWaveAudioHandler
- _gestionarErrorReproduccion() emite AudioProcessingState.error al UI,
  loggea el error y resetea el player a estado idle limpio
- _mensajeAmigable() traduce códigos ERROR_CODE_IO_*, ERROR_CODE_PARSING_*,
  ERROR_CODE_DECODING_* y mensajes de Cleartext/HandshakeException a texto legible
- EstadoRadio.reproducir() captura la excepción y cancela el timer si estaba activo
- EstadoRadio escucha el estadoStream y cancela timer ante cualquier error

**Fix 3 — Artwork con certificado autofirmado:**
- errorWidget en CachedNetworkImage captura HandshakeException silenciosamente
- Muestra _iconoFallback (icono de radio) en lugar de imagen rota
- El error de artwork no se propaga ni interrumpe la reproducción

**Fix 4 — UI consistente en estado de error:**
- PantallaReproductor._Controles muestra mensaje + botón Reintentar en error
- PantallaReproductor._Artwork muestra overlay wifi_off en estado de error
- MiniReproductor muestra botón refresh (reintentar) en estado de error
- EstadoReproduccion.error ya estaba definido; ahora el estadoStream lo emite
- Timer cancelado automáticamente cuando la reproducción falla
- Test de smoke corregido (boilerplate MyApp → placeholder válido)

Fixes: cleartext HTTP, cert autofirmado, ExoPlayer TYPE_SOURCE, UI inconsistente
2026-04-04 20:43:56 +02:00
ShanaiaBot 5fd3d6deb9 feat(v0.3.0): ecualizador + favoritos en tarjeta + emisoras custom + export/import + fix MainActivity
Flutter CI/CD — PluriWave / Test + Build (push) Has been cancelled
- MainActivity: extiende AudioServiceActivity (fix pantalla en blanco)
- ServicioAudio: AndroidEqualizer en AudioPipeline, aplicarPreset(), setBanda()
- PresetEcualizador: modelo independiente (Flat/Rock/Pop/BassBoost/Jazz/Voz)
- EcualizadorWidget: 5 sliders verticales + PresetsEcualizadorWidget
- TarjetaEmisora: botón favorito visible en grid y lista (toggle con SnackBar)
- EstadoRadio: emisoras custom (CRUD), export/import JSON v1, presets por emisora
- PantallaAjustes: ecualizador interactivo, form añadir emisora, backup export/import
- pubspec: +file_picker ^8.1.7, +uuid ^4.5.1
2026-04-04 19:17:40 +02:00
Kira (Agent) 81db383a47 fix(v0.3.0): audio background + emisoras rotas + errores toast + icono
- ServicioAudio: delega a PluriWaveAudioHandler (audio_service) para
  mantener audio vivo en background. AudioService.init() en main.dart.
  onTaskRemoved() libera player. mediaItem con nombre/artista/artwork.
- ServicioRadio: lastcheckok=1 en todas las peticiones — solo emisoras
  verificadas como funcionales por Radio Browser API.
- EstadoRadio: errorStream (broadcast) para errores de reproducción y
  búsqueda. App.dart suscribe y muestra SnackBar flotante 3s.
  Los errores de carga de lista siguen como banner inline.
- Icono: generado con SDXL (morado, ondas radio blancas, Material You).
  5 densidades Android (48-192px), ic_launcher_round añadido.
2026-04-04 18:09:59 +02:00
Kira (Agent) e9d1f67aa4 feat(mvp): PluriWave Fase 1 — estructura completa de la app
Flutter CI/CD — PluriWave / Test + Build (pull_request) Has been cancelled
- Modelo Emisora: campos completos Radio Browser API (fromApi + fromMap)
- ServicioRadio: cliente Radio Browser API (populares, tendencias, buscar por nombre/país/idioma/tag)
- ServicioAudio: just_audio + audio_service wrapper (play/pause/stop/toggle, fade, background handler)
- ServicioTimer: countdown con fade out gradual (15/30/60/90 min)
- ServicioFavoritos: actualizado a v2 con campos codec/bitrate/votes/clickcount
- EstadoRadio: ChangeNotifier global con Provider
- PantallaInicio: grid emisoras populares, chips género, shimmer loading, pull-to-refresh
- PantallaBuscar: SearchBar + filtros país/idioma, lista resultados
- PantallaFavoritos: ReorderableListView + swipe-to-delete (Dismissible)
- TarjetaEmisora: card + modo compacto ListTile, cached_network_image, shimmer fallback
- MiniReproductor: barra inferior persistente con stream de estado
- app.dart: MaterialApp + Provider + NavigationBar + timer dialog
- main.dart: punto de entrada limpio
- AndroidManifest.xml: permisos INTERNET + FOREGROUND_SERVICE + audio_service receivers
2026-04-04 17:15:18 +02:00
ShanaiaBot 00b11f88df init: proyecto Flutter PluriWave — radio mundial con ecualizador
- Flutter create con org es.freetimelab
- pubspec.yaml con dependencias core (just_audio, audio_service, provider, sqflite)
- README con features y stack
- Assets preparados
2026-04-04 15:35:02 +02:00