Conversation
…nization, and UI rendering
- **Album Art Management**:
- Introduces `AlbumArtCacheManager` to implement an LRU eviction policy, limiting cache size to 200MB and adding orphaned file cleanup.
- Implements `MediaMetadataRetrieverPool` to reuse retriever instances, reducing the overhead of creation/destruction during mass scans.
- Updates `AlbumArtUtils` to utilize the new pooling mechanism and LRU tracking.
- **Sync & Data Performance**:
- Optimizes `SyncWorker` genre detection by replacing sequential N+1 queries with a parallelized batch approach using coroutines and semaphores.
- Adds automated cleanup of orphaned cache files at the end of synchronization cycles.
- **UI & Battery Optimization**:
- Refactors `OptimizedAlbumArt` to use Coil's native crossfade within `SubcomposeAsyncImage`, eliminating redundant recompositions caused by the `Crossfade` wrapper.
- Updates `PlayerViewModel` to dynamically scale playback progress polling intervals based on UI visibility (200ms when expanded, up to 1000ms when hidden) to save battery.
- **Documentation**:
- Adds `LOCAL_OPTIM.md` providing a comprehensive audit of identified bottlenecks, memory leaks, and a roadmap for future architectural improvements.
…ent content - Updates `HorizontalPager` in `LibraryScreen` to use `beyondViewportPageCount = 1`. - This ensures adjacent tabs are pre-loaded, reducing UI lag and providing a smoother experience when switching between library categories. - Marks the corresponding task as completed in `LOCAL_OPTIM.md`.
…behavior - Extracts `QueueHeader` and `QueueControlsToolbar` into standalone composables to minimize unnecessary recompositions. - Moves the `QueueUiItem` data class to top-level and marks it with `@Immutable` to ensure stable keys and better integration with Compose's runtime. - Refactors the queue control logic into a dedicated `QueueControlsToolbar` component, simplifying the main bottom sheet layout. - Updates `LOCAL_OPTIM.md` to reflect the completion of the `QueueBottomSheet` refactoring task.
- Introduces `SongEngagementEntity` and `EngagementDao` to manage song play statistics (play count, duration, and last played timestamp) within the Room database. - Implements an atomic `recordPlay` operation in `EngagementDao` using SQLite's `ON CONFLICT` clause for better performance and data integrity. - Updates `PixelPlayDatabase` to version 12 and adds `MIGRATION_11_12` to create the `song_engagements` table. - Refactors `DailyMixManager` to utilize `EngagementDao` for data persistence, replacing the legacy `song_scores.json` storage. - Implements a one-time migration flow in `DailyMixManager` to move existing engagement data from JSON to the database, creating a backup of the legacy file. - Provides `EngagementDao` via Hilt in `AppModule`. - Marks engagement migration and `SyncWorker` optimization as completed in `LOCAL_OPTIM.md`.
- Replaces `mutableMapOf` with a `LinkedHashMap` configured for access-order LRU behavior. - Limits the cache size to 30 entries to prevent unbounded memory growth from stored `ColorSchemePair` objects. - Ensures the least recently used album color schemes are automatically evicted when the limit is exceeded.
…oved modularity - Moves `ListeningStatsTracker` and its helper class `ActiveSession` to a dedicated file to reduce the size and complexity of `PlayerViewModel`. - Updates `ListeningStatsTracker` to use dependency injection for `DailyMixManager` and `PlaybackStatsRepository`. - Implements an `initialize` method to provide a `CoroutineScope` for asynchronous stat recording. - Integrates the refactored tracker into `PlayerViewModel` via constructor injection and initializes it within the `init` block. - Adjusts song deletion logic in `PlayerViewModel` to finalize the current listening session using the new tracker instance.
- Extracts color scheme generation logic from `PlayerViewModel` into a dedicated `ColorSchemeProcessor` singleton for improved modularity. - Implements an in-memory `LruCache` to avoid redundant database reads for recently accessed album art themes. - Introduces `processingMutex` and state tracking to prevent duplicate extraction tasks for the same URI. - Optimizes performance by reducing bitmap size (128x128) and ensuring CPU-bound work runs on `Dispatchers.Default` while I/O operations use `Dispatchers.IO`. - Simplifies `PlayerViewModel` by delegating color extraction and caching responsibilities to the new processor. - Enhances reliability with improved mapping between database entities and Compose `ColorScheme` objects.
- Extracts color scheme generation logic from `PlayerViewModel` into a dedicated `ColorSchemeProcessor` singleton for improved modularity. - Implements an in-memory `LruCache` to avoid redundant database reads for recently accessed album art themes. - Introduces `processingMutex` and state tracking to prevent duplicate extraction tasks for the same URI. - Optimizes performance by reducing bitmap size (128x128) and ensuring CPU-bound work runs on `Dispatchers.Default` while I/O operations use `Dispatchers.IO`. - Simplifies `PlayerViewModel` by delegating color extraction and caching responsibilities to the new processor. - Enhances reliability with improved mapping between database entities and Compose `ColorScheme` objects.
- Introduces `LyricsStateHolder` to encapsulate lyrics loading, search UI state, and sync offset management, improving `PlayerViewModel` modularity. - Implements `LyricsLoadCallback` to handle communication between the new state holder and `PlayerViewModel`, ensuring `StablePlayerState` updates correctly when lyrics load. - Offloads lyrics search state management and sync offset persistence to `LyricsStateHolder`, reducing the complexity of `PlayerViewModel`. - Updates `PlayerViewModel` to delegate lyrics-related actions (loading, searching, canceling, and resetting state) to the new component. - Maintains existing functionality for lyrics sync offsets, remote searches, and manual imports while simplifying the view model's internal logic.
- Extracts Cast/Chromecast state logic from `PlayerViewModel` into a new `CastStateHolder` class to improve modularity and maintainability. - Migrates `CastSession`, `CastPlayer`, connection states, and remote playback positioning into the new state holder. - Updates `PlayerViewModel` to utilize `CastStateHolder` for managing remote playback sessions, progress updates, and route selection. - Refines session transfer logic to use unified state setters for `isCastConnecting`, `pendingCastRouteId`, and `isRemotePlaybackActive`. - Simplifies the cleanup process by implementing `clearRemoteState()` in the state holder to reset all Cast-related metadata.
…tateHolder - Introduces `QueueStateHolder` to encapsulate the logic for storing original queue orders and creating shuffled queues. - Decouples manual shuffle/unshuffle state management from `PlayerViewModel` to improve modularity and maintainability. - Updates `PlayerViewModel` to delegate queue state persistence and retrieval to the new `QueueStateHolder`. - Refines shuffle logic to use `QueueStateHolder.createShuffledQueue()` for consistent queue generation while maintaining the current song index. - Ensures original queue names and song lists are correctly preserved when toggling shuffle or switching playback sources.
- **Library & UI Performance**:
- Migrates the main song list in `LibraryScreen` to Paging 3, utilizing `LazyPagingItems` to efficiently handle large music libraries with reduced memory overhead.
- Replaces the full `allSongs` list in `PlayerUiState` with a lightweight `songCount` to prevent unnecessary state copying.
- Introduces skeleton loading states and shimmer effects for songs, albums, and artists to improve perceived performance during initial data fetches.
- **Sync & Metadata Optimizations**:
- Enhances `SyncWorker` by implementing a 1-hour TTL cache for the genre map, avoiding redundant MediaStore queries during incremental syncs.
- Integrates `MediaMetadataRetrieverPool` into `AudioMetaUtils` to manage retriever instances more efficiently.
- **Data Layer Improvements**:
- Adds `getSongsPaginated` to `MusicDao` for Room-integrated pagination and `getRandomSongs` for performance-oriented database-level shuffling.
- Updates `MusicRepository` and its implementation to support paginated flows and directory-filtered song counts.
- **Refactoring & Maintenance**:
- Moves `allSongs` to a standalone `StateFlow` in `PlayerViewModel` to decouple it from the main UI state.
- Updates `LOCAL_OPTIM.md` to reflect completion of Phase 2 and Phase 3 architectural optimizations.
- Adds required Paging 3 dependencies to `build.gradle.kts` and `libs.versions.toml`.
- Reduces coroutine overhead in `SettingsViewModel` and `SetupViewModel` by replacing numerous individual `collect` calls with grouped `combine()` operations. - Implements `SettingsUiUpdate` and `SetupPrefsUpdate` helper classes to manage consolidated UI state updates efficiently. - Offloads heavy genre sorting computation in `PlayerViewModel` to `Dispatchers.Default` to prevent main thread blocking. - Updates `LOCAL_OPTIM.md` to reflect the implementation of collector consolidation and the migration of `allSongs` out of `PlayerUiState` for improved RAM usage.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7e614fd98
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pagingData | ||
| .filter { entity -> | ||
| val normalizedParent = normalizePath(entity.parentDirectoryPath) | ||
| !resolver.isBlocked(normalizedParent) | ||
| } |
There was a problem hiding this comment.
Apply directory filter before paging to avoid empty results
The directory filter is applied after paging via PagingData.filter, which means the paging source still loads unfiltered pages. With placeholders disabled, LazyPagingItems only triggers further loads when the UI accesses indices near the end of the current list; if the first page is entirely filtered out (e.g., first 50 songs are in blocked directories), the list ends up with itemCount == 0 and no further loads are triggered, so the library appears empty even though allowed songs exist later. To avoid this, apply the filter in the paging source (e.g., query using allowed/blocked rules or a custom PagingSource that skips blocked rows while maintaining load keys).
Useful? React with 👍 / 👎.
Latest optimization changes (read commits)