D-pad left/right now seeks -10s/+10s both when controls are hidden
and when the seek bar is focused. Controls auto-focus the seek bar
when shown, removing the need to press OK first to interact.
- Add animated focus feedback (scale, border, background) to all interactive
TV UI components: PosterCard, GhostIconButton, MediaActionButton,
MediaResumeButton, PurefinIconButton, TvHomeDrawer nav items,
TvHomeSections cards, TvPlayerScreen track/queue panels
- Refactor EpisodeScreen, MovieScreen, and SeriesScreen from Scaffold +
verticalScroll to LazyColumn layout with Box-overlaid top bar
- Switch MovieComponents and MovieScreen from MovieUiModel to core Movie model
- Conditionally render cast sections only when cast is non-empty
The detectTapGestures and drag awaitEachGesture handlers competed for
pointer events, causing drag() to sometimes end prematurely and the
seek to not fire. A shared dragActive flag now gates tap/double-tap
callbacks so they are suppressed during active drags.
- Create InMemoryMediaRepository to hold in-memory media state (movies,
series, episodes), lazy series content loading, and watch progress updates
- InMemoryAppContentRepository delegates MediaRepository via Kotlin's by
delegation, injecting InMemoryMediaRepository as a composite component
- CompositeMediaRepository now uses InMemoryMediaRepository directly as the
online repository instead of AppContentRepository
Extract AppContentRepository interface from MediaRepository for home/library-specific features.
CompositeMediaRepository delegates to OfflineMediaRepository or AppContentRepository based on
UserSessionRepository.isOfflineMode, allowing content ViewModels to use a single MediaRepository
interface that automatically switches data source.
- Remove RoomMediaLocalDataSource dependency from InMemoryMediaRepository;
libraries, movies, series, and episodes are now held in MutableStateFlows
- observeSeriesWithContent derives from _series flow via map {}
- updateWatchProgress mutates _movies / _episodes in-memory
- ensureSeriesContentLoaded checks and updates _series in-memory
- Reorganize Room package: move entities/DAOs from local/room to room/entity and room/dao
- Add PlayerRoute to the Route sealed interface
- Refactor PlayerViewModel to expose loadMedia() for external callers
- Add onPlay() to EpisodeScreenViewModel and MovieScreenViewModel
- Wire play/resume buttons in TV episode and movie screens
- Create TvPlayerScreen with TV-optimized controls: seek bar, playback
buttons, track selection panels, queue panel, and state cards
- Register PlayerRoute in TvRouteEntryBuilder and TvNavigationModule
- Add media3-ui dependency to app-tv module
- Created MovieScreen, MovieComponents for movie detail view
- Created SeriesScreen, SeriesComponents for series detail with season selection
- Created EpisodeScreen, EpisodeComponents for episode detail view
- Copied all required common UI components from mobile app
- Updated TvNavigationModule with route entry builders
- Updated TvRouteEntryBuilder with new screen entries
- Added missing launcher resources for TV app build
- All screens reuse ViewModels from :feature:shared module
- TV app now supports browsing movies, series, and episodes
Creates a new :app-tv application module that reuses shared business
logic from :core:model, :core:data, :core:player, and :feature:shared,
with its own TV-specific UI layer.
- TvApplication + TvActivity with full Hilt + Navigation3 wiring
- TvHomePage reusing HomePageViewModel from :feature:shared
- Copied common UI components (PosterCard, WaitingScreen, etc.)
- Copied login screen and form components
- TV navigation module with Home + Login route entry builders
- Material3 theme copied from mobile (ready for TV customization)
- AndroidManifest with LAUNCHER + LEANBACK_LAUNCHER intent filters
- Excludes :feature:download (downloads are mobile-only)
Vertical drag gestures (brightness/volume) were not consuming pointer
events, causing detectTapGestures to fire onTap and toggle the player
overlay when the gesture ended.
ensureReady() had a race condition where concurrent callers from init
block and ViewModels would all run the loading logic simultaneously.
Added Mutex to ensure only the first caller loads; others await.
Also added a cooldown to refreshHomeData() since LifecycleResumeEffect
fires immediately on first composition, triggering a redundant reload
right after the initial load completes.
Switch seek parameters from CLOSEST_SYNC to PREVIOUS_SYNC so seeks always
land at or before the requested position, preventing subtitle cues from
being skipped. On backward seek discontinuity, briefly disable and re-enable
the text track to flush TextRenderer state so the current cue is re-delivered.