Update App.kt

This commit is contained in:
AdityasahuX07 2026-05-12 23:03:49 +05:30 committed by GitHub
parent c29f0cc064
commit 753f19d581
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -123,7 +123,6 @@ import com.nuvio.app.features.library.LibrarySection
import com.nuvio.app.features.library.LibrarySourceMode import com.nuvio.app.features.library.LibrarySourceMode
import com.nuvio.app.features.library.LibraryScreen import com.nuvio.app.features.library.LibraryScreen
import com.nuvio.app.features.library.toLibraryItem import com.nuvio.app.features.library.toLibraryItem
import com.nuvio.app.features.library.toMetaPreview
import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepository import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepository
import com.nuvio.app.features.player.PlayerLaunch import com.nuvio.app.features.player.PlayerLaunch
import com.nuvio.app.features.player.PlayerLaunchStore import com.nuvio.app.features.player.PlayerLaunchStore
@ -385,8 +384,8 @@ fun App() {
val cachedProfiles = profileState.profiles val cachedProfiles = profileState.profiles
val allowOfflineProfileAccess = val allowOfflineProfileAccess =
cachedProfiles.isNotEmpty() && cachedProfiles.isNotEmpty() &&
authState !is AuthState.Authenticated && authState !is AuthState.Authenticated &&
networkStatusUiState.condition != NetworkCondition.Online networkStatusUiState.condition != NetworkCondition.Online
when (authState) { when (authState) {
is AuthState.Loading -> { is AuthState.Loading -> {
@ -660,7 +659,7 @@ private fun MainAppContent(
NetworkCondition.Unknown, NetworkCondition.Unknown,
NetworkCondition.Checking, NetworkCondition.Checking,
-> Unit -> Unit
} }
lastNetworkToastCondition = condition.name lastNetworkToastCondition = condition.name
@ -677,7 +676,7 @@ private fun MainAppContent(
when (networkStatusUiState.condition) { when (networkStatusUiState.condition) {
NetworkCondition.Unknown, NetworkCondition.Unknown,
NetworkCondition.Checking, NetworkCondition.Checking,
-> return@LaunchedEffect -> return@LaunchedEffect
NetworkCondition.Online -> { NetworkCondition.Online -> {
offlineLaunchRouteHandled = true offlineLaunchRouteHandled = true
@ -685,7 +684,7 @@ private fun MainAppContent(
NetworkCondition.NoInternet, NetworkCondition.NoInternet,
NetworkCondition.ServersUnreachable, NetworkCondition.ServersUnreachable,
-> { -> {
offlineLaunchRouteHandled = true offlineLaunchRouteHandled = true
val hasPlayableDownload = downloadsUiState.completedItems.any { val hasPlayableDownload = downloadsUiState.completedItems.any {
DownloadsRepository.playableLocalFileUri(it) != null DownloadsRepository.playableLocalFileUri(it) != null
@ -729,24 +728,29 @@ private fun MainAppContent(
} }
} }
LaunchedEffect(navController) { LaunchedEffect(navController) {
AppDeepLinkRepository.pendingDeepLink.collectLatest { deepLink -> AppDeepLinkRepository.pendingDeepLink.collectLatest { deepLink ->
when (deepLink) { when (deepLink) {
is AppDeepLink.Meta -> { is AppDeepLink.Meta -> {
selectedTab = AppScreenTab.Home selectedTab = AppScreenTab.Home
navController.navigate(DetailRoute(type = deepLink.type, id = deepLink.id)) { navController.navigate(DetailRoute(type = deepLink.type, id = deepLink.id)) {
launchSingleTop = true launchSingleTop = true
}
AppDeepLinkRepository.markConsumed(deepLink)
} }
AppDeepLinkRepository.markConsumed(deepLink)
}
AppDeepLink.Downloads -> { AppDeepLink.Downloads -> {
selectedTab = AppScreenTab.Settings selectedTab = AppScreenTab.Settings
navController.navigate(DownloadsSettingsRoute) { navController.navigate(DownloadsSettingsRoute) {
launchSingleTop = true launchSingleTop = true
}
AppDeepLinkRepository.markConsumed(deepLink)
} }
AppDeepLinkRepository.markConsumed(deepLink)
null -> Unit
} }
}
}
fun launchPlaybackWithDownloadPreference( fun launchPlaybackWithDownloadPreference(
type: String, type: String,
@ -809,69 +813,90 @@ private fun MainAppContent(
return return
} }
} }
}
}
fun launchPlaybackWithDownloadPreference( val streamLaunchId = StreamLaunchStore.put(
type: String, StreamLaunch(
videoId: String, type = type,
parentMetaId: String, videoId = videoId,
parentMetaType: String, parentMetaId = parentMetaId,
title: String, parentMetaType = parentMetaType,
logo: String?, title = title,
poster: String?, logo = logo,
background: String?, poster = poster,
seasonNumber: Int?, background = background,
episodeNumber: Int?, seasonNumber = seasonNumber,
episodeTitle: String?, episodeNumber = episodeNumber,
episodeThumbnail: String?, episodeTitle = episodeTitle,
pauseDescription: String?, episodeThumbnail = episodeThumbnail,
resumePositionMs: Long?, pauseDescription = pauseDescription,
resumeProgressFraction: Float?, resumePositionMs = if (startFromBeginning) 0L else resumePositionMs,
manualSelection: Boolean, resumeProgressFraction = targetResumeProgressFraction,
startFromBeginning: Boolean, manualSelection = manualSelection,
) { startFromBeginning = startFromBeginning,
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 navController.navigate(
if (!localSourceUrl.isNullOrBlank()) { StreamRoute(launchId = streamLaunchId),
val launchId = PlayerLaunchStore.put( )
PlayerLaunch( }
title = title,
sourceUrl = localSourceUrl, val onPlay: (String, String, String, String, String, String?, String?, String?, Int?, Int?, String?, String?, String?, Long?) -> Unit =
sourceHeaders = emptyMap(), { type, videoId, parentMetaId, parentMetaType, title, logo, poster, background, seasonNumber, episodeNumber, episodeTitle, episodeThumbnail, pauseDescription, resumePositionMs ->
sourceResponseHeaders = emptyMap(), launchPlaybackWithDownloadPreference(
logo = logo, type = type,
poster = poster, videoId = videoId,
background = background, parentMetaId = parentMetaId,
seasonNumber = seasonNumber, parentMetaType = parentMetaType,
episodeNumber = episodeNumber, title = title,
episodeTitle = episodeTitle, logo = logo,
episodeThumbnail = episodeThumbnail, poster = poster,
streamTitle = downloadedItem.streamTitle.ifBlank { title }, background = background,
streamSubtitle = downloadedItem.streamSubtitle, seasonNumber = seasonNumber,
pauseDescription = pauseDescription, episodeNumber = episodeNumber,
providerName = downloadedItem.providerName.ifBlank { downloadedProviderLabel }, episodeTitle = episodeTitle,
providerAddonId = downloadedItem.providerAddonId, episodeThumbnail = episodeThumbnail,
contentType = type, pauseDescription = pauseDescription,
videoId = videoId, resumePositionMs = resumePositionMs,
parentMetaId = parentMetaId, resumeProgressFraction = null,
parentMetaType = parentMetaType, manualSelection = false,
initialPositionMs = targetResumePositionMs, startFromBeginning = false,
initialProgressFraction = targetResumeProgressFraction,
),
) )
navController.navigate(PlayerRoute(launchId = launchId))
return
} }
val onPlayManually: (String, String, String, String, String, String?, String?, String?, Int?, Int?, String?, String?, String?, Long?) -> Unit =
{ type, videoId, parentMetaId, parentMetaType, title, logo, poster, background, seasonNumber, episodeNumber, episodeTitle, episodeThumbnail, pauseDescription, resumePositionMs ->
launchPlaybackWithDownloadPreference(
type = type,
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 = resumePositionMs,
resumeProgressFraction = null,
manualSelection = true,
startFromBeginning = false,
)
}
val onCatalogClick: (HomeCatalogSection) -> Unit = { section ->
navController.navigate(
CatalogRoute(
title = section.title,
subtitle = section.subtitle,
manifestUrl = section.manifestUrl,
type = section.type,
catalogId = section.catalogId,
supportsPagination = section.supportsPagination,
),
)
} }
val librarySectionSubtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) { val librarySectionSubtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) {
@ -893,111 +918,56 @@ private fun MainAppContent(
) )
} }
val onPlayManually: (String, String, String, String, String, String?, String?, String?, Int?, Int?, String?, String?, String?, Long?) -> Unit = val openContinueWatching: (ContinueWatchingItem, Boolean, Boolean) -> Unit = { item, manualSelection, startFromBeginning ->
{ type, videoId, parentMetaId, parentMetaType, title, logo, poster, background, seasonNumber, episodeNumber, episodeTitle, episodeThumbnail, pauseDescription, resumePositionMs ->
launchPlaybackWithDownloadPreference( launchPlaybackWithDownloadPreference(
type = type, type = item.parentMetaType,
videoId = videoId, videoId = item.videoId,
parentMetaId = parentMetaId, parentMetaId = item.parentMetaId,
parentMetaType = parentMetaType, parentMetaType = item.parentMetaType,
title = title, title = item.title,
logo = logo, logo = item.logo,
poster = poster, poster = item.poster,
background = background, background = item.background,
seasonNumber = seasonNumber, seasonNumber = item.seasonNumber,
episodeNumber = episodeNumber, episodeNumber = item.episodeNumber,
episodeTitle = episodeTitle, episodeTitle = item.episodeTitle,
episodeThumbnail = episodeThumbnail, episodeThumbnail = item.episodeThumbnail,
pauseDescription = pauseDescription, pauseDescription = item.pauseDescription,
resumePositionMs = resumePositionMs, resumePositionMs = item.resumePositionMs,
resumeProgressFraction = null, resumeProgressFraction = item.resumeProgressFraction,
manualSelection = true, manualSelection = manualSelection,
startFromBeginning = false, startFromBeginning = startFromBeginning,
) )
} }
val onCatalogClick: (HomeCatalogSection) -> Unit = { section -> val onContinueWatchingClick: (ContinueWatchingItem) -> Unit = { item ->
navController.navigate( openContinueWatching(item, false, false)
CatalogRoute( }
title = section.title,
subtitle = section.subtitle,
manifestUrl = section.manifestUrl,
type = section.type,
catalogId = section.catalogId,
supportsPagination = section.supportsPagination,
),
)
}
val librarySectionSubtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) { val onContinueWatchingStartFromBeginning: (ContinueWatchingItem) -> Unit = { item ->
stringResource(Res.string.compose_catalog_subtitle_trakt_library) openContinueWatching(item, false, true)
} else { }
stringResource(Res.string.compose_catalog_subtitle_library)
}
val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section -> val onContinueWatchingPlayManually: (ContinueWatchingItem) -> Unit = { item ->
navController.navigate( openContinueWatching(item, true, false)
CatalogRoute( }
title = section.displayTitle,
subtitle = librarySectionSubtitle,
manifestUrl = INTERNAL_LIBRARY_MANIFEST_URL,
type = section.items.firstOrNull()?.type ?: "movie",
catalogId = section.type,
supportsPagination = false,
),
)
}
val openContinueWatching: (ContinueWatchingItem, Boolean, Boolean) -> Unit = { item, manualSelection, startFromBeginning -> val onContinueWatchingLongPress: (ContinueWatchingItem) -> Unit = { item ->
launchPlaybackWithDownloadPreference( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
type = item.parentMetaType, selectedContinueWatchingForActions = item
videoId = item.videoId, }
parentMetaId = item.parentMetaId,
parentMetaType = item.parentMetaType,
title = item.title,
logo = item.logo,
poster = item.poster,
background = item.background,
seasonNumber = item.seasonNumber,
episodeNumber = item.episodeNumber,
episodeTitle = item.episodeTitle,
episodeThumbnail = item.episodeThumbnail,
pauseDescription = item.pauseDescription,
resumePositionMs = item.resumePositionMs,
resumeProgressFraction = item.resumeProgressFraction,
manualSelection = manualSelection,
startFromBeginning = startFromBeginning,
)
}
val onContinueWatchingClick: (ContinueWatchingItem) -> Unit = { item -> Box(
openContinueWatching(item, false, false) modifier = Modifier
} .fillMaxSize()
.background(MaterialTheme.colorScheme.background),
val onContinueWatchingStartFromBeginning: (ContinueWatchingItem) -> Unit = { item -> ) {
openContinueWatching(item, false, true) SharedTransitionLayout {
} NavHost(
navController = navController,
val onContinueWatchingPlayManually: (ContinueWatchingItem) -> Unit = { item -> startDestination = TabsRoute,
openContinueWatching(item, true, false) modifier = Modifier.fillMaxSize(),
} ) {
val onContinueWatchingLongPress: (ContinueWatchingItem) -> Unit = { item ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedContinueWatchingForActions = item
}
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
SharedTransitionLayout {
NavHost(
navController = navController,
startDestination = TabsRoute,
modifier = Modifier.fillMaxSize(),
) {
composable<TabsRoute> { composable<TabsRoute> {
PlatformBackHandler( PlatformBackHandler(
enabled = true, enabled = true,
@ -1303,8 +1273,8 @@ private fun MainAppContent(
} }
val shouldResolveEpisodeVideoId = val shouldResolveEpisodeVideoId =
launch.parentMetaId != null && launch.parentMetaId != null &&
launch.seasonNumber != null && launch.seasonNumber != null &&
launch.episodeNumber != null launch.episodeNumber != null
var effectiveVideoId by rememberSaveable( var effectiveVideoId by rememberSaveable(
launch.videoId, launch.videoId,
launch.parentMetaId, launch.parentMetaId,
@ -1345,7 +1315,7 @@ private fun MainAppContent(
?.videos ?.videos
?.firstOrNull { video -> ?.firstOrNull { video ->
video.season == launch.seasonNumber && video.season == launch.seasonNumber &&
video.episode == launch.episodeNumber video.episode == launch.episodeNumber
} }
?.id ?.id
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
@ -1818,8 +1788,8 @@ private fun MainAppContent(
}, },
) )
} }
}
} }
}
NuvioPosterActionSheet( NuvioPosterActionSheet(
item = selectedPosterForActions, item = selectedPosterForActions,
@ -1857,94 +1827,73 @@ private fun MainAppContent(
}.onFailure { error -> }.onFailure { error ->
pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed) pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed)
} }
}.onFailure { error -> pickerPending = false
pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed)
} }
pickerPending = false
} }
} }
} },
}, onToggleWatched = {
onToggleWatched = { selectedPosterForActions?.let { preview ->
selectedPosterForActions?.let { preview -> coroutineScope.launch {
coroutineScope.launch { WatchingActions.togglePosterWatched(preview)
WatchingActions.togglePosterWatched(preview) }
} }
} },
}, )
)
NuvioContinueWatchingActionSheet( NuvioContinueWatchingActionSheet(
item = selectedContinueWatchingForActions, item = selectedContinueWatchingForActions,
showManualPlayOption = StreamAutoPlayPolicy.isEffectivelyEnabled(playerSettingsUiState), showManualPlayOption = StreamAutoPlayPolicy.isEffectivelyEnabled(playerSettingsUiState),
onDismiss = { selectedContinueWatchingForActions = null }, onDismiss = { selectedContinueWatchingForActions = null },
onOpenDetails = { onOpenDetails = {
selectedContinueWatchingForActions?.let { item -> selectedContinueWatchingForActions?.let { item ->
navController.navigate( navController.navigate(
DetailRoute( DetailRoute(
type = item.parentMetaType, type = item.parentMetaType,
id = item.parentMetaId, id = item.parentMetaId,
),
)
}
},
onStartFromBeginning = selectedContinueWatchingForActions
?.takeIf { !it.isNextUp }
?.let { item -> { onContinueWatchingStartFromBeginning(item) } },
onPlayManually = selectedContinueWatchingForActions
?.let { item -> { onContinueWatchingPlayManually(item) } },
onRemove = {
selectedContinueWatchingForActions?.let { item ->
if (item.isNextUp) {
ContinueWatchingPreferencesRepository.addDismissedNextUpKey(
nextUpDismissKey(
item.parentMetaId,
item.nextUpSeedSeasonNumber,
item.nextUpSeedEpisodeNumber,
), ),
) )
} else {
WatchProgressRepository.removeProgress(contentId = item.parentMetaId)
} }
} },
}, onStartFromBeginning = selectedContinueWatchingForActions
) ?.takeIf { !it.isNextUp }
?.let { item -> { onContinueWatchingStartFromBeginning(item) } },
onPlayManually = selectedContinueWatchingForActions
?.let { item -> { onContinueWatchingPlayManually(item) } },
onRemove = {
selectedContinueWatchingForActions?.let { item ->
if (item.isNextUp) {
ContinueWatchingPreferencesRepository.addDismissedNextUpKey(
nextUpDismissKey(
item.parentMetaId,
item.nextUpSeedSeasonNumber,
item.nextUpSeedEpisodeNumber,
),
)
} else {
WatchProgressRepository.removeProgress(contentId = item.parentMetaId)
}
}
},
)
TraktListPickerDialog( TraktListPickerDialog(
visible = showLibraryListPicker, visible = showLibraryListPicker,
title = pickerTitle, title = pickerTitle,
tabs = pickerTabs, tabs = pickerTabs,
membership = pickerMembership, membership = pickerMembership,
isPending = pickerPending, isPending = pickerPending,
errorMessage = pickerError, errorMessage = pickerError,
onToggle = { listKey -> onToggle = { listKey ->
pickerMembership = pickerMembership.toMutableMap().apply { pickerMembership = pickerMembership.toMutableMap().apply {
this[listKey] = !(this[listKey] == true) this[listKey] = !(this[listKey] == true)
} }
}, },
onDismiss = { onDismiss = {
if (!pickerPending) { if (!pickerPending) {
showLibraryListPicker = false
pickerItem = null
pickerError = null
}
},
onSave = {
val item = pickerItem ?: return@TraktListPickerDialog
coroutineScope.launch {
pickerPending = true
pickerError = null
runCatching {
LibraryRepository.applyMembershipChanges(
item = item,
desiredMembership = pickerMembership,
)
}.onSuccess {
showLibraryListPicker = false showLibraryListPicker = false
pickerItem = null pickerItem = null
pickerError = null pickerError = null
}.onFailure { error ->
pickerError = error.message ?: getString(Res.string.trakt_lists_update_failed)
} }
}, },
onSave = { onSave = {
@ -1984,22 +1933,22 @@ private fun MainAppContent(
}, },
) )
androidx.compose.animation.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
visible = !initialHomeReady || profileSwitchLoading, visible = !initialHomeReady || profileSwitchLoading,
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut(androidx.compose.animation.core.tween(400)), exit = fadeOut(androidx.compose.animation.core.tween(400)),
) { ) {
AppLaunchOverlay(modifier = Modifier.fillMaxSize()) AppLaunchOverlay(modifier = Modifier.fillMaxSize())
} }
// Auto-dismiss profile switch overlay // Auto-dismiss profile switch overlay
if (profileSwitchLoading) { if (profileSwitchLoading) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// Brief loading screen while home refreshes for the new profile // Brief loading screen while home refreshes for the new profile
kotlinx.coroutines.delay(1200) kotlinx.coroutines.delay(1200)
profileSwitchLoading = false profileSwitchLoading = false
}
} }
}
NuvioFloatingPrompt( NuvioFloatingPrompt(
visible = resumePromptItem != null, visible = resumePromptItem != null,
@ -2019,19 +1968,19 @@ private fun MainAppContent(
.zIndex(15f), .zIndex(15f),
) )
NuvioToastHost( NuvioToastHost(
modifier = Modifier modifier = Modifier
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.zIndex(20f), .zIndex(20f),
) )
AppUpdaterHost( AppUpdaterHost(
controller = appUpdaterController, controller = appUpdaterController,
modifier = Modifier modifier = Modifier
.align(Alignment.Center) .align(Alignment.Center)
.zIndex(25f), .zIndex(25f),
) )
} }
} }
@Composable @Composable
@ -2063,7 +2012,6 @@ 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,
@ -2113,7 +2061,6 @@ private fun AppTabHost(
LibraryScreen( LibraryScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
onPosterClick = onLibraryPosterClick, onPosterClick = onLibraryPosterClick,
onPosterLongClick = onLibraryPosterLongClick,
onSectionViewAllClick = onLibrarySectionViewAllClick, onSectionViewAllClick = onLibrarySectionViewAllClick,
) )
} }