Refactor playback launch logic and navigation

Refactor launchPlaybackWithDownloadPreference to use StreamLaunchStore for launching streams and update related navigation logic.
This commit is contained in:
AdityasahuX07 2026-04-25 15:36:42 +05:30 committed by GitHub
parent 727b48bbc8
commit 5b5a8b6906
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -656,66 +656,7 @@ private fun MainAppContent(
AppDeepLinkRepository.markConsumed(deepLink) AppDeepLinkRepository.markConsumed(deepLink)
} }
fun launchPlaybackWithDownloadPreference( null -> Unit
type: String,
videoId: String,
parentMetaId: String,
parentMetaType: String,
title: String,
logo: String?,
poster: String?,
background: String?,
seasonNumber: Int?,
episodeNumber: Int?,
episodeTitle: String?,
episodeThumbnail: String?,
pauseDescription: String?,
resumePositionMs: Long?,
resumeProgressFraction: Float?,
manualSelection: Boolean,
startFromBeginning: Boolean,
) {
val targetResumePositionMs = if (startFromBeginning) 0L else (resumePositionMs ?: 0L)
val targetResumeProgressFraction = if (startFromBeginning) null else resumeProgressFraction
if (!manualSelection) {
val downloadedItem = DownloadsRepository.findPlayableDownload(
parentMetaId = parentMetaId,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
videoId = videoId,
)
val localSourceUrl = downloadedItem?.localFileUri
if (!localSourceUrl.isNullOrBlank()) {
val launchId = PlayerLaunchStore.put(
PlayerLaunch(
title = title,
sourceUrl = localSourceUrl,
sourceHeaders = emptyMap(),
sourceResponseHeaders = emptyMap(),
logo = logo,
poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
streamTitle = downloadedItem.streamTitle.ifBlank { title },
streamSubtitle = downloadedItem.streamSubtitle,
pauseDescription = pauseDescription,
providerName = downloadedItem.providerName.ifBlank { downloadedProviderLabel },
providerAddonId = downloadedItem.providerAddonId,
contentType = type,
videoId = videoId,
parentMetaId = parentMetaId,
parentMetaType = parentMetaType,
initialPositionMs = targetResumePositionMs,
initialProgressFraction = targetResumeProgressFraction,
),
)
navController.navigate(PlayerRoute(launchId = launchId))
return
}
} }
} }
} }
@ -767,7 +708,7 @@ private fun MainAppContent(
streamTitle = downloadedItem.streamTitle.ifBlank { title }, streamTitle = downloadedItem.streamTitle.ifBlank { title },
streamSubtitle = downloadedItem.streamSubtitle, streamSubtitle = downloadedItem.streamSubtitle,
pauseDescription = pauseDescription, pauseDescription = pauseDescription,
providerName = downloadedItem.providerName.ifBlank { "Downloaded" }, providerName = downloadedItem.providerName.ifBlank { downloadedProviderLabel },
providerAddonId = downloadedItem.providerAddonId, providerAddonId = downloadedItem.providerAddonId,
contentType = type, contentType = type,
videoId = videoId, videoId = videoId,
@ -782,22 +723,52 @@ private fun MainAppContent(
} }
} }
val librarySectionSubtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) { val streamLaunchId = StreamLaunchStore.put(
stringResource(Res.string.compose_catalog_subtitle_trakt_library) StreamLaunch(
} else { type = type,
stringResource(Res.string.compose_catalog_subtitle_library) videoId = videoId,
} parentMetaId = parentMetaId,
parentMetaType = parentMetaType,
title = title,
logo = logo,
poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
pauseDescription = pauseDescription,
resumePositionMs = if (startFromBeginning) 0L else resumePositionMs,
resumeProgressFraction = targetResumeProgressFraction,
manualSelection = manualSelection,
startFromBeginning = startFromBeginning,
),
)
navController.navigate(
StreamRoute(launchId = streamLaunchId),
)
}
val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section -> val onPlay: (String, String, String, String, String, String?, String?, String?, Int?, Int?, String?, String?, String?, Long?) -> Unit =
navController.navigate( { type, videoId, parentMetaId, parentMetaType, title, logo, poster, background, seasonNumber, episodeNumber, episodeTitle, episodeThumbnail, pauseDescription, resumePositionMs ->
CatalogRoute( launchPlaybackWithDownloadPreference(
title = section.displayTitle, type = type,
subtitle = librarySectionSubtitle, videoId = videoId,
manifestUrl = INTERNAL_LIBRARY_MANIFEST_URL, parentMetaId = parentMetaId,
type = section.items.firstOrNull()?.type ?: "movie", parentMetaType = parentMetaType,
catalogId = section.type, title = title,
supportsPagination = false, logo = logo,
), poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
pauseDescription = pauseDescription,
resumePositionMs = resumePositionMs,
resumeProgressFraction = null,
manualSelection = false,
startFromBeginning = false,
) )
} }
@ -837,15 +808,17 @@ private fun MainAppContent(
) )
} }
val librarySectionSubtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) {
stringResource(Res.string.compose_catalog_subtitle_trakt_library)
} else {
stringResource(Res.string.compose_catalog_subtitle_library)
}
val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section -> val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section ->
navController.navigate( navController.navigate(
CatalogRoute( CatalogRoute(
title = section.displayTitle, title = section.displayTitle,
subtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) { subtitle = librarySectionSubtitle,
"Trakt Library"
} else {
"Library"
},
manifestUrl = INTERNAL_LIBRARY_MANIFEST_URL, manifestUrl = INTERNAL_LIBRARY_MANIFEST_URL,
type = section.items.firstOrNull()?.type ?: "movie", type = section.items.firstOrNull()?.type ?: "movie",
catalogId = section.type, catalogId = section.type,
@ -986,7 +959,7 @@ private fun MainAppContent(
}, },
onLibraryPosterLongClick = { item -> onLibraryPosterLongClick = { item ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterForActions = item.toMetaPreview() // reuse existing sheet selectedPosterForActions = item.toMetaPreview()
}, },
onLibrarySectionViewAllClick = onLibrarySectionViewAllClick, onLibrarySectionViewAllClick = onLibrarySectionViewAllClick,
onContinueWatchingClick = onContinueWatchingClick, onContinueWatchingClick = onContinueWatchingClick,
@ -1080,11 +1053,11 @@ private fun MainAppContent(
castAvatarTransitionKey = avatarTransitionKey, castAvatarTransitionKey = avatarTransitionKey,
preferCrew = person.role?.let { preferCrew = person.role?.let {
it.equals("Director", ignoreCase = true) || it.equals("Director", ignoreCase = true) ||
it.equals(directorRole, ignoreCase = true) || it.equals(directorRole, ignoreCase = true) ||
it.equals("Writer", ignoreCase = true) || it.equals("Writer", ignoreCase = true) ||
it.equals(writerRole, ignoreCase = true) || it.equals(writerRole, ignoreCase = true) ||
it.equals("Creator", ignoreCase = true) it.equals("Creator", ignoreCase = true)
|| it.equals(creatorRole, ignoreCase = true) || it.equals(creatorRole, ignoreCase = true)
} ?: false, } ?: false,
), ),
) )
@ -1689,34 +1662,15 @@ private fun MainAppContent(
) )
} == true, } == true,
onDismiss = { selectedPosterForActions = null }, onDismiss = { selectedPosterForActions = null },
// TAP → direct save/remove (watchlist or local library)
onToggleLibrary = { onToggleLibrary = {
selectedPosterForActions?.let { preview -> selectedPosterForActions?.let { preview ->
val libraryItem = preview.toLibraryItem(savedAtEpochMs = 0L) val libraryItem = preview.toLibraryItem(savedAtEpochMs = 0L)
if (!isTraktConnected) { LibraryRepository.toggleSaved(libraryItem)
LibraryRepository.toggleSaved(libraryItem)
} else {
// Tap = directly toggle watchlist (first tab)
coroutineScope.launch {
runCatching {
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
val tabs = LibraryRepository.traktListTabs()
val watchlistKey = tabs.firstOrNull()?.key
if (watchlistKey != null) {
val currentlyIn = snapshot[watchlistKey] == true
val desired = tabs.associate { tab ->
tab.key to if (tab.key == watchlistKey) !currentlyIn else (snapshot[tab.key] == true)
}
}.onFailure { error ->
pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed)
}
}.onFailure {
LibraryRepository.toggleSaved(libraryItem)
}
}
}
} }
}, },
onOpenListPicker = if (isTraktConnected) { // LONG PRESS → open Trakt list picker (only when Trakt is connected)
onToggleLibraryLongClick = if (isTraktConnected) {
{ {
selectedPosterForActions?.let { preview -> selectedPosterForActions?.let { preview ->
val libraryItem = preview.toLibraryItem(savedAtEpochMs = 0L) val libraryItem = preview.toLibraryItem(savedAtEpochMs = 0L)
@ -1736,7 +1690,7 @@ private fun MainAppContent(
tab.key to (snapshot[tab.key] == true) tab.key to (snapshot[tab.key] == true)
} }
}.onFailure { error -> }.onFailure { error ->
pickerError = error.message ?: "Failed to load Trakt lists" pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed)
} }
pickerPending = false pickerPending = false
} }
@ -1822,45 +1776,27 @@ private fun MainAppContent(
pickerItem = null pickerItem = null
pickerError = null pickerError = null
}.onFailure { error -> }.onFailure { error ->
pickerError = error.message ?: "Failed to update Trakt lists" pickerError = error.message ?: getString(Res.string.trakt_lists_update_failed)
} }
}, pickerPending = false
onSave = { }
val item = pickerItem ?: return@TraktListPickerDialog },
coroutineScope.launch { )
pickerPending = true
pickerError = null
runCatching {
LibraryRepository.applyMembershipChanges(
item = item,
desiredMembership = pickerMembership,
)
}.onSuccess {
showLibraryListPicker = false
pickerItem = null
pickerError = null
}.onFailure { error ->
pickerError = error.message ?: getString(Res.string.trakt_lists_update_failed)
}
pickerPending = false
}
},
)
NuvioStatusModal( NuvioStatusModal(
title = stringResource(Res.string.app_exit_title), title = stringResource(Res.string.app_exit_title),
message = stringResource(Res.string.app_exit_message), message = stringResource(Res.string.app_exit_message),
isVisible = showExitConfirmation, isVisible = showExitConfirmation,
confirmText = stringResource(Res.string.action_yes), confirmText = stringResource(Res.string.action_yes),
dismissText = stringResource(Res.string.action_no), dismissText = stringResource(Res.string.action_no),
onConfirm = { onConfirm = {
showExitConfirmation = false showExitConfirmation = false
platformExitApp() platformExitApp()
}, },
onDismiss = { onDismiss = {
showExitConfirmation = false showExitConfirmation = false
}, },
) )
androidx.compose.animation.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
visible = !initialHomeReady || profileSwitchLoading, visible = !initialHomeReady || profileSwitchLoading,
@ -1879,23 +1815,23 @@ private fun MainAppContent(
} }
} }
NuvioFloatingPrompt( NuvioFloatingPrompt(
visible = resumePromptItem != null, visible = resumePromptItem != null,
imageUrl = resumePromptItem?.poster ?: resumePromptItem?.imageUrl, imageUrl = resumePromptItem?.poster ?: resumePromptItem?.imageUrl,
title = resumePromptItem?.title.orEmpty(), title = resumePromptItem?.title.orEmpty(),
subtitle = resumePromptItem?.let { localizedContinueWatchingSubtitle(it) }.orEmpty(), subtitle = resumePromptItem?.let { localizedContinueWatchingSubtitle(it) }.orEmpty(),
progressFraction = resumePromptItem?.progressFraction ?: 0f, progressFraction = resumePromptItem?.progressFraction ?: 0f,
actionLabel = stringResource(Res.string.resume_prompt_action), actionLabel = stringResource(Res.string.resume_prompt_action),
onAction = { onAction = {
val item = resumePromptItem ?: return@NuvioFloatingPrompt val item = resumePromptItem ?: return@NuvioFloatingPrompt
resumePromptItem = null resumePromptItem = null
openContinueWatching(item, false, false) openContinueWatching(item, false, false)
}, },
onDismiss = { resumePromptItem = null }, onDismiss = { resumePromptItem = null },
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.zIndex(15f), .zIndex(15f),
) )
NuvioToastHost( NuvioToastHost(
modifier = Modifier modifier = Modifier
@ -1940,6 +1876,7 @@ private fun AppTabHost(
onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterClick: ((MetaPreview) -> Unit)? = null,
onPosterLongClick: ((MetaPreview) -> Unit)? = null, onPosterLongClick: ((MetaPreview) -> Unit)? = null,
onLibraryPosterClick: ((LibraryItem) -> Unit)? = null, onLibraryPosterClick: ((LibraryItem) -> Unit)? = null,
onLibraryPosterLongClick: ((LibraryItem) -> Unit)? = null,
onLibrarySectionViewAllClick: ((LibrarySection) -> Unit)? = null, onLibrarySectionViewAllClick: ((LibrarySection) -> Unit)? = null,
onContinueWatchingClick: ((ContinueWatchingItem) -> Unit)? = null, onContinueWatchingClick: ((ContinueWatchingItem) -> Unit)? = null,
onContinueWatchingLongPress: ((ContinueWatchingItem) -> Unit)? = null, onContinueWatchingLongPress: ((ContinueWatchingItem) -> Unit)? = null,
@ -1956,7 +1893,6 @@ private fun AppTabHost(
onCollectionsSettingsClick: () -> Unit = {}, onCollectionsSettingsClick: () -> Unit = {},
onFolderClick: ((collectionId: String, folderId: String) -> Unit)? = null, onFolderClick: ((collectionId: String, folderId: String) -> Unit)? = null,
onInitialHomeContentRendered: () -> Unit = {}, onInitialHomeContentRendered: () -> Unit = {},
onLibraryPosterLongClick: ((LibraryItem) -> Unit)? = null,
) { ) {
val tabStateHolder = rememberSaveableStateHolder() val tabStateHolder = rememberSaveableStateHolder()