chore: baseline main con los 4 PRs abiertos a upstream + build type personal - #27
Merged
Conversation
Pulls the inline search field out of SongPickerBottomSheet into a standalone SearchFilterTextField composable so it can be reused by other song-list screens without duplicating the input styling.
Pure, testable predicate for filtering a song by title or artist, using the same field scope as the existing global song search. Will back the upcoming in-playlist search filter.
Adds a persistent SearchFilterTextField above the song list that filters the currently displayed songs by title/artist, scoped to the songs already loaded for this playlist (local or streaming-sourced). Reorder mode is not yet guarded against an active filter; that lands in the next commit.
Hides the Play/Shuffle/Add/Remove/Reorder action row while a search filter is active, and force-exits reorder/remove mode as soon as the user starts typing. This keeps drag-to-reorder (index-based) from ever operating on a filtered view of the playlist. Also adds a dedicated "no results for query" empty state, distinct from the existing "playlist has no songs" state.
Encapsulates the displayedSongs filtering block from PlaylistDetailScreen into a pure, list-level utility so it's unit-testable in isolation, on top of the existing per-song matchesTitleOrArtist predicate. No functional change to PlaylistDetailScreen; same filtering behavior, now backed by 5 additional unit tests covering blank query, ordering, no-match, empty-list, and mixed title/artist matches.
Exercises the real PlaylistDetailScreen composable end-to-end with relaxed mocks for PlaylistViewModel/PlayerViewModel, covering: - typing a query filters the visible songs - a non-matching query shows the "no results" empty state - clearing the query restores the full list and the actions row - the actions row (Play it/Shuffle/Add/Remove/Reorder) is hidden while a search query is active - reorder mode is force-disabled when search starts and stays disabled after clearing the query (not just hidden momentarily) - tapping a filtered song plays the FULL unfiltered playlist starting from that song (the highest-risk behavior of this feature) - an empty playlist never shows the search field Swaps androidTestImplementation from io.mockk:mockk to io.mockk:mockk-android, required for mocking concrete classes (PlaylistViewModel/PlayerViewModel) on-device; plain mockk only ships a JVM instrumentation agent that ART can't load.
Search filter in the playlist detail screen (PR PixelPlayerHQ#2641 against upstream/master).
Release-like build type (initWith release: minified, shrunk resources,
signed with the release keystore) for day-to-day development builds,
so they're representative of real performance instead of running
unoptimized debug builds.
applicationIdSuffix ".dev" + versionNameSuffix "-dev" + a distinct
app_name ("PixelPlayer [DEV]", same override pattern already used by
debug's "PixelPlayer [D]") let it install side by side with both the
official release build and debug, without applicationId collisions.
Not intended for upstream; this build type only lives on this fork.
Needed because wear shares applicationId with app and must be able to coexist with/pair to the .dev phone build. matchingFallbacks falls back to :shared's release variant, same mechanism app's benchmark build type already relies on for the same dependency.
Personal (release-like) build type for both app and wear, fork-only, not intended for an upstream PR.
app/src/debug/res only overrode app_name for de/fr/ko/nb/ru (+ the default bucket). It missed ar/es/in/it/tr/zh-rCN, which is a problem because those locales' main/values-<locale>/strings.xml files also define app_name (untranslated, same 'PixelPlayer' value) — and a locale-qualified resource always wins over a less-specific one regardless of which source set (main vs debug) contributed it. Net effect: on a device set to any of those 6 locales, a debug build silently loses its '[D]' name and looks identical to the release build. Verified with a real device set to es-MX before this fix (showed plain 'PixelPlayer') and confirmed via aapt2 badging on the rebuilt APK after.
fix(debug): add missing locale overrides for the [D] app name suffix
Same root cause fixed in debug (PR PixelPlayerHQ#2705 upstream): every locale whose main/values-<locale>/strings.xml defines its own app_name wins over an unqualified override from a build-type source set on a device set to that locale. Adds app/src/personal/res/values-<locale>/strings.xml for all 11 locales that override app_name, so 'PixelPlayer [DEV]' shows reliably regardless of device language. Verified via aapt2 dump badging on the rebuilt personal APK: every locale bucket now resolves to 'PixelPlayer [DEV]'. Fork-only, same as the rest of the personal build type.
matchesTitleOrArtist only ignored case, not accents, so searching "que gan" inside a playlist never matched a song titled "Qué ganas de...". Adds String.foldDiacritics() (NFD normalize + strip combining marks) in Extensions.kt as a general-purpose reusable utility, and uses it on both the query and the title/artist before comparing. Verified this is unrelated to the main library search: that path goes through SQLite FTS4 (MusicDao.searchSongsMatch) with the unicode61 tokenizer, which already folds diacritics by default — confirmed with a standalone sqlite3 repro (MATCH 'que* AND gan*' already finds 'Qué ganas de bailar' pre-existing, no code change needed there).
buildSongSearchMatchQuery/buildSongTitleSearchMatchQuery joined query tokens with the literal " AND " keyword (e.g. "que* AND ganas*"). That keyword only behaves as a boolean operator on SQLite builds compiled with SQLITE_ENABLE_FTS3_PARENTHESIS - not guaranteed on every Android device. Without it, "AND" is parsed as an ordinary search term, so a multi-word query would only match rows that literally contained the word "and", silently breaking multi-word search entirely on affected devices. Confirmed on a real device (Galaxy S25 Ultra, SQLite 3.44.5, no FTS3_PARENTHESIS support): searching a two-word query against a title that doesn't contain "and" returned zero results, regardless of case or accents, while the exact same content was trivially findable via a single-token query or via the always-available implicit-AND syntax (space-separated terms, no keyword). Fix: join tokens with a plain space instead. Implicit AND is base FTS3/4 syntax, supported unconditionally on every SQLite build. Also extracts the shared tokenization logic into one helper (buildFtsMatchQuery) to remove the duplication between the two query builders. Adds MusicDaoQueryBuilderTest (unit, verifies the query string shape) and a MusicDaoTest regression case that runs the real query against a real FTS4 table - which is what actually caught this, since a plain string-comparison test wouldn't exercise the SQLite engine at all.
…, reliability hardening Adds a full Wear OS companion experience for offline listening: transfer playlists from the phone to the watch (transcode, chunked transfer, resumable across process death), play them locally on the watch without the phone nearby, and control the phone remotely when it's in range — plus several rounds of reliability and performance hardening driven by real hardware testing (audio offload with HAL-reset fallback, mid-song stall recovery, playlist-sync and transfer-outcome acks so the phone never assumes success without watch confirmation, and user-facing performance toggles — album art, dynamic color, play-button animation — for constrained watch hardware). This is a squashed extraction of ~55 commits (PR8, PR24, PR25, PR26) built up across a longer development history on a personal fork, isolated onto master here as a single self-contained changeset for review. Excludes everything from that fork unrelated to this feature (a playlist search filter, an FTS search fix, personal app branding) — none of it touched. Known gap carried over from the fork: FAVORITES_SYNC_REQUEST/STATE are declared but not fully wired (no manifest registration, no phone-side handler) — pre-existing, unrelated to this feature, left as-is rather than silently finishing it inside an unrelated PR.
Nueve defectos encontrados al revisar upstream-proposal/wear-companion-app contra master, todos verificados contra el código antes de corregirse. Altos: - Una transferencia guardada en biblioteca podía quedarse para siempre en STATUS_AWAITING_WATCH_ACK si el reporte del reloj nunca llegaba, dejando clavada la notificación en primer plano y el indicador de "enviando". El timeout vive en PhoneWatchTransferStateStore para cubrir también el camino de canción suelta, que no tiene ningún await propio del que colgar uno. - El interruptor de tema dinámico no hacía nada mientras la carátula estuviera activada: el tema caía a derivar la paleta del bitmap, por la ruta más cara que el propio interruptor existe para evitar. Medios: - Un batch reanudado tras la muerte del proceso perdía su intent para siempre si el reloj no estaba al alcance en ese arranque. Ahora sobrevive y caduca a los 7 días, arrastrando la marca de tiempo original. - Con bitrate desconocido (el caso normal: sólo el escaneo profundo lo captura) se re-codificaba toda la biblioteca a AAC 128k. Ahora se sondea el archivo antes de decidir. - Un stream cerrado por el watchdog podía confirmar como completo un archivo truncado, que quedaba en Room como reproducible y el teléfono no volvía a intentar. - ERROR_ALREADY_ON_WATCH se contaba como fallo: se re-transcodificaba y se reofrecía la misma canción para reportarla luego como fallida. - Un Transformer atascado colgaba el batch indefinidamente. Bajos: - Un batch cancelado antes de arrancar se reportaba como "Completado". - Al restaurar la reproducción, la posición guardada se aplicaba a otra canción si la original había sido borrada del reloj.
Reportado en dispositivo: con "mostrar carátula", "color dinámico" y "animación del botón" desactivados en el teléfono, la reproducción local del reloj seguía mostrando las tres cosas. Los gates estaban bien —los verifiqué de punta a punta: el repositorio local, el ViewModel y PlayerScreen aplican los tres flags correctamente. Lo que fallaba era que el reloj nunca se enteraba del valor: - El reloj sólo aprendía los ajustes por push (onDataChanged), y un DataItem con contenido idéntico no genera evento. Reinstalar la app del reloj —cada build de depuración— borra su copia local y la deja en los valores por defecto (todo activado), y el reanuncio del teléfono al abrir la pantalla de ajustes es byte a byte el mismo DataItem, así que se descarta y el reloj se queda desincronizado para siempre. Ahora el reloj lee el DataItem publicado al arrancar el proceso. - Además, la escritura del ajuste corría en el scope del WearableListenerService, que onDestroy cancela en cuanto el callback retorna: la escritura en DataStore competía con ese teardown y podía perderse. Al ser un evento único, perderla es permanente. Ahora se escribe en línea sobre el hilo del callback.
… en dispositivo Del reporte de pruebas físicas: - La carátula y los colores aparecían al iniciar la reproducción y se quitaban uno o dos segundos después. La lectura de los ajustes al arrancar tarda lo suyo (llamada a Play Services), y hasta entonces se actuaba con los valores cacheados. Ahora hay un estado "resuelto": los consumidores tratan "aún no lo sé" como desactivado, así que el trabajo caro se difiere en vez de hacerse y deshacerse. Con red de seguridad para que un fallo no deje la carátula suprimida para siempre. - El teléfono nunca procesaba la respuesta de biblioteca del reloj: el manifiesto enruta /watch_library_state a WearCommandReceiver, pero su `when` no tenía rama y caía en "Unknown message path". La consecuencia es que el teléfono sólo sabía lo que él mismo había transferido y nunca se corregía —canciones borradas en el reloj, o el reloj reinstalado con su biblioteca en blanco, seguían contando como presentes—, y también que isWatchLibraryResolved nunca se ponía a true, así que el resume siempre agotaba sus 10 s de espera. - "Actualizar en el reloj" salía para playlists nunca enviadas, porque se decidía con "alguna canción de la playlist está en el reloj". Ahora el reloj informa qué playlists tiene y la etiqueta usa esa señal. - El chip de error del reloj no decía qué canción falló, y sobrevivía a un reintento exitoso. Ahora recupera el título y una transferencia completada descarta los errores previos de esa misma canción.
…dispositivo Revisión de código de la rama contra master (9 hallazgos, todos con escenario de fallo verificado) más lo que salió de la prueba física: - Cuelgue permanente en STATUS_AWAITING_WATCH_ACK y notificación clavada. - El interruptor de tema dinámico no hacía nada con la carátula activada. - Intent de batch perdido al reanudar sin reloj al alcance; ahora caduca a los 7 días en vez de descartarse. - Todo se re-codificaba a AAC 128k por bitrate desconocido; ahora se sondea el archivo. - Un archivo truncado podía confirmarse como completo. - ERROR_ALREADY_ON_WATCH se contaba como fallo y disparaba un reintento. - Un Transformer atascado colgaba el batch indefinidamente. - Batch cancelado antes de arrancar reportado como "Completado". - Posición restaurada sobre la canción equivocada. Y del dispositivo: - Los tres ajustes de rendimiento nunca llegaban al reloj (sólo push, que el Data Layer descarta si el DataItem no cambia, más una escritura que competía con el teardown del servicio). - El teléfono no procesaba /watch_library_state, así que nunca conocía la biblioteca real del reloj. - "Actualizar en el reloj" para playlists nunca enviadas. - Carátula y colores aplicándose y quitándose al iniciar la reproducción. - Chip de error sin título de canción y persistente tras un reintento exitoso.
…to chore/main-baseline
…cales' into chore/main-baseline
…keyword' into chore/main-baseline
…-app' into chore/main-baseline # Conflicts: # app/src/main/java/com/theveloper/pixelplay/presentation/screens/PlaylistDetailScreen.kt
…s' into chore/main-baseline
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Qué integra
Puebla
main(hasta ahora igual aupstream/master) con todo el trabajo propio ya abierto como PR contraPixelPlayerHQ:master, más lo que es exclusivamente de este fork.feature/playlist-song-search(PR upstream feat(playlist): search filter in playlist detail screen PixelPlayerHQ/PixelPlayer#2641)bugfix/debug-app-name-missing-locales(PR upstream fix(debug): add missing locale overrides for the [D] app name suffix PixelPlayerHQ/PixelPlayer#2705)bugfix/fts-multiword-search-and-keyword(PR upstream fix(search): use implicit AND in FTS queries, not the AND keyword PixelPlayerHQ/PixelPlayer#2706)upstream-proposal/wear-companion-app(PR upstream feat(wear): Wear OS companion app — playlist transfer, local playback & remote control PixelPlayerHQ/PixelPlayer#2733)chore/personal-build-type-locales(build typepersonal, fork-only, arrastrachore/personal-build-type)chore(tooling): ignoragraphify-out/y.claude/(fork-only)Conflicto resuelto
PlaylistDetailScreen.kt: el PR de búsqueda envuelve la fila de reproducción y la fila de acciones enif (searchQuery.isBlank()); el PR de Wear consolidó el espaciado de esas mismas filas en una variablesectionSpacing. Se combinaron ambos cambios conservando el comportamiento de las dos features.Verificación
:app:assembleDebug,:wear:assembleDebug: BUILD SUCCESSFUL:wear:testDebugUnitTest,:shared:testDebugUnitTest: en verde (rerun forzado):app:testDebugUnitTest: 5 fallos, confirmados idénticos enupstream/masterlimpio (worktree aislado) — preexistentes, no introducidos aquí:wear:lintDebug: en verde:app:lintDebug: 3722 errores, primer error confirmado idéntico enupstream/master— sin baseline configurado y sin paso de lint en ninguno de los 5 workflows de CI del proyecto. Deuda preexistente, no introducida aquíTags de respaldo antes de tocar nada:
backup/dev-20260827,backup/dev-personal-20260827.