feat: remaining places to use bottomsheetmodal

This commit is contained in:
tapframe 2026-05-17 19:34:02 +05:30
parent 7a5c531a24
commit f8eb52c299
3 changed files with 92 additions and 89 deletions

View file

@ -126,6 +126,7 @@ 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
@ -276,6 +277,12 @@ data class CatalogRoute(
val genre: String? = null, val genre: String? = null,
) )
private data class PosterActionTarget(
val preview: MetaPreview,
val libraryItem: LibraryItem? = null,
val libraryListKey: String? = null,
)
enum class AppScreenTab { enum class AppScreenTab {
Home, Home,
Search, Search,
@ -556,7 +563,7 @@ private fun MainAppContent(
}.collectAsStateWithLifecycle() }.collectAsStateWithLifecycle()
val liquidGlassNativeTabBarSupported = remember { isLiquidGlassNativeTabBarSupported() } val liquidGlassNativeTabBarSupported = remember { isLiquidGlassNativeTabBarSupported() }
var showExitConfirmation by rememberSaveable { mutableStateOf(false) } var showExitConfirmation by rememberSaveable { mutableStateOf(false) }
var selectedPosterForActions by remember { mutableStateOf<MetaPreview?>(null) } var selectedPosterActionTarget by remember { mutableStateOf<PosterActionTarget?>(null) }
var selectedContinueWatchingForActions by remember { mutableStateOf<ContinueWatchingItem?>(null) } var selectedContinueWatchingForActions by remember { mutableStateOf<ContinueWatchingItem?>(null) }
var showLibraryListPicker by remember { mutableStateOf(false) } var showLibraryListPicker by remember { mutableStateOf(false) }
var pickerItem by remember { mutableStateOf<LibraryItem?>(null) } var pickerItem by remember { mutableStateOf<LibraryItem?>(null) }
@ -1136,11 +1143,19 @@ private fun MainAppContent(
}, },
onPosterLongClick = { meta -> onPosterLongClick = { meta ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterForActions = meta selectedPosterActionTarget = PosterActionTarget(preview = meta)
}, },
onLibraryPosterClick = { item -> onLibraryPosterClick = { item ->
navController.navigate(DetailRoute(type = item.type, id = item.id)) navController.navigate(DetailRoute(type = item.type, id = item.id))
}, },
onLibraryPosterLongClick = { item, section ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterActionTarget = PosterActionTarget(
preview = item.toMetaPreview(),
libraryItem = item,
libraryListKey = section.type,
)
},
onLibrarySectionViewAllClick = onLibrarySectionViewAllClick, onLibrarySectionViewAllClick = onLibrarySectionViewAllClick,
onContinueWatchingClick = onContinueWatchingClick, onContinueWatchingClick = onContinueWatchingClick,
onContinueWatchingLongPress = onContinueWatchingLongPress, onContinueWatchingLongPress = onContinueWatchingLongPress,
@ -1832,6 +1847,18 @@ private fun MainAppContent(
onPosterClick = { meta -> onPosterClick = { meta ->
navController.navigate(DetailRoute(type = meta.type, id = meta.id)) navController.navigate(DetailRoute(type = meta.type, id = meta.id))
}, },
onPosterLongClick = { meta ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterActionTarget = if (route.manifestUrl == INTERNAL_LIBRARY_MANIFEST_URL) {
PosterActionTarget(
preview = meta,
libraryItem = meta.toLibraryItem(savedAtEpochMs = 0L),
libraryListKey = route.catalogId,
)
} else {
PosterActionTarget(preview = meta)
}
},
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) )
} }
@ -1997,20 +2024,45 @@ private fun MainAppContent(
} }
NuvioPosterActionSheet( NuvioPosterActionSheet(
item = selectedPosterForActions, item = selectedPosterActionTarget?.preview,
isSaved = selectedPosterForActions?.let { preview -> isSaved = selectedPosterActionTarget?.preview?.let { preview ->
LibraryRepository.isSaved(preview.id, preview.type) LibraryRepository.isSaved(preview.id, preview.type)
} == true, } == true,
isWatched = selectedPosterForActions?.let { preview -> isWatched = selectedPosterActionTarget?.preview?.let { preview ->
WatchingState.isPosterWatched( WatchingState.isPosterWatched(
watchedKeys = watchedUiState.watchedKeys, watchedKeys = watchedUiState.watchedKeys,
item = preview, item = preview,
) )
} == true, } == true,
onDismiss = { selectedPosterForActions = null }, onDismiss = { selectedPosterActionTarget = null },
onToggleLibrary = { onToggleLibrary = {
selectedPosterForActions?.let { preview -> selectedPosterActionTarget?.let { target ->
val libraryItem = preview.toLibraryItem(savedAtEpochMs = 0L) val preview = target.preview
val libraryItem = target.libraryItem ?: preview.toLibraryItem(savedAtEpochMs = 0L)
if (target.libraryItem != null) {
if (isTraktLibrarySource) {
coroutineScope.launch {
runCatching {
val listKey = target.libraryListKey
if (listKey.isNullOrBlank()) {
val currentMembership = LibraryRepository.getMembershipSnapshot(libraryItem)
LibraryRepository.applyMembershipChanges(
item = libraryItem,
desiredMembership = currentMembership.mapValues { false },
)
} else {
LibraryRepository.removeFromList(libraryItem, listKey)
}
}.onFailure { error ->
NuvioToastController.show(
error.message ?: getString(Res.string.trakt_lists_update_failed),
)
}
}
} else {
LibraryRepository.remove(libraryItem.id)
}
} else {
if (!isTraktLibrarySource) { if (!isTraktLibrarySource) {
LibraryRepository.toggleSaved(libraryItem) LibraryRepository.toggleSaved(libraryItem)
} else { } else {
@ -2036,9 +2088,10 @@ private fun MainAppContent(
} }
} }
} }
}
}, },
onToggleWatched = { onToggleWatched = {
selectedPosterForActions?.let { preview -> selectedPosterActionTarget?.preview?.let { preview ->
coroutineScope.launch { coroutineScope.launch {
WatchingActions.togglePosterWatched(preview) WatchingActions.togglePosterWatched(preview)
} }
@ -2223,6 +2276,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, LibrarySection) -> 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,
@ -2276,6 +2330,7 @@ private fun AppTabHost(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
scrollToTopRequests = libraryScrollToTopRequests, scrollToTopRequests = libraryScrollToTopRequests,
onPosterClick = onLibraryPosterClick, onPosterClick = onLibraryPosterClick,
onPosterLongClick = onLibraryPosterLongClick,
onSectionViewAllClick = onLibrarySectionViewAllClick, onSectionViewAllClick = onLibrarySectionViewAllClick,
) )
} }

View file

@ -72,6 +72,7 @@ fun CatalogScreen(
genre: String? = null, genre: String? = null,
onBack: () -> Unit, onBack: () -> Unit,
onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterClick: ((MetaPreview) -> Unit)? = null,
onPosterLongClick: ((MetaPreview) -> Unit)? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val uiState by CatalogRepository.uiState.collectAsStateWithLifecycle() val uiState by CatalogRepository.uiState.collectAsStateWithLifecycle()
@ -187,6 +188,7 @@ fun CatalogScreen(
cornerRadiusDp = posterCardStyle.cornerRadiusDp, cornerRadiusDp = posterCardStyle.cornerRadiusDp,
hideLabels = posterCardStyle.hideLabelsEnabled, hideLabels = posterCardStyle.hideLabelsEnabled,
onClick = onPosterClick?.let { { it(item) } }, onClick = onPosterClick?.let { { it(item) } },
onLongClick = onPosterLongClick?.let { { it(item) } },
) )
} }
if (uiState.isLoading) { if (uiState.isLoading) {
@ -257,6 +259,7 @@ private fun CatalogPosterTile(
cornerRadiusDp: Int, cornerRadiusDp: Int,
hideLabels: Boolean, hideLabels: Boolean,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
) { ) {
Column( Column(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
@ -267,7 +270,7 @@ private fun CatalogPosterTile(
.aspectRatio(item.posterShape.catalogAspectRatio()) .aspectRatio(item.posterShape.catalogAspectRatio())
.clip(RoundedCornerShape(cornerRadiusDp.dp)) .clip(RoundedCornerShape(cornerRadiusDp.dp))
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surface)
.posterCardClickable(onClick = onClick, onLongClick = null), .posterCardClickable(onClick = onClick, onLongClick = onLongClick),
) { ) {
if (item.poster != null) { if (item.poster != null) {
AsyncImage( AsyncImage(

View file

@ -25,8 +25,6 @@ import com.nuvio.app.core.network.NetworkStatusRepository
import com.nuvio.app.core.ui.NuvioScreen import com.nuvio.app.core.ui.NuvioScreen
import com.nuvio.app.core.ui.NuvioNetworkOfflineCard import com.nuvio.app.core.ui.NuvioNetworkOfflineCard
import com.nuvio.app.core.ui.NuvioScreenHeader import com.nuvio.app.core.ui.NuvioScreenHeader
import com.nuvio.app.core.ui.NuvioStatusModal
import com.nuvio.app.core.ui.NuvioToastController
import com.nuvio.app.core.ui.NuvioViewAllPillSize import com.nuvio.app.core.ui.NuvioViewAllPillSize
import com.nuvio.app.core.ui.NuvioShelfSection import com.nuvio.app.core.ui.NuvioShelfSection
import com.nuvio.app.core.ui.nuvioBlockPointerPassthrough import com.nuvio.app.core.ui.nuvioBlockPointerPassthrough
@ -38,20 +36,14 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import nuvio.composeapp.generated.resources.* import nuvio.composeapp.generated.resources.*
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
private data class LibraryRemovalTarget(
val item: LibraryItem,
val listKey: String? = null,
val listTitle: String? = null,
)
@Composable @Composable
fun LibraryScreen( fun LibraryScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
scrollToTopRequests: Flow<Unit> = emptyFlow(), scrollToTopRequests: Flow<Unit> = emptyFlow(),
onPosterClick: ((LibraryItem) -> Unit)? = null, onPosterClick: ((LibraryItem) -> Unit)? = null,
onPosterLongClick: ((LibraryItem, LibrarySection) -> Unit)? = null,
onSectionViewAllClick: ((LibrarySection) -> Unit)? = null, onSectionViewAllClick: ((LibrarySection) -> Unit)? = null,
) { ) {
val uiState by remember { val uiState by remember {
@ -59,7 +51,6 @@ fun LibraryScreen(
LibraryRepository.uiState LibraryRepository.uiState
}.collectAsStateWithLifecycle() }.collectAsStateWithLifecycle()
val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle() val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle()
var pendingRemovalTarget by remember { mutableStateOf<LibraryRemovalTarget?>(null) }
var observedOfflineState by remember { mutableStateOf(false) } var observedOfflineState by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
@ -187,64 +178,18 @@ fun LibraryScreen(
sections = uiState.sections, sections = uiState.sections,
onPosterClick = onPosterClick, onPosterClick = onPosterClick,
onSectionViewAllClick = onSectionViewAllClick, onSectionViewAllClick = onSectionViewAllClick,
onPosterLongClick = { item, section -> onPosterLongClick = onPosterLongClick,
pendingRemovalTarget = if (isTraktSource) {
LibraryRemovalTarget(
item = item,
listKey = section.type,
listTitle = section.displayTitle,
)
} else {
LibraryRemovalTarget(item = item)
}
},
) )
} }
} }
} }
NuvioStatusModal(
title = stringResource(Res.string.library_remove_title),
message = pendingRemovalTarget?.let { target ->
val listTitle = target.listTitle
if (listTitle.isNullOrBlank()) {
stringResource(Res.string.library_remove_message, target.item.name)
} else {
stringResource(Res.string.library_remove_from_list_message, target.item.name, listTitle)
}
}.orEmpty(),
isVisible = pendingRemovalTarget != null,
confirmText = stringResource(Res.string.library_remove_confirm),
dismissText = stringResource(Res.string.action_cancel),
onConfirm = {
val target = pendingRemovalTarget
pendingRemovalTarget = null
target?.let {
val listKey = target.listKey
if (listKey.isNullOrBlank()) {
LibraryRepository.remove(target.item.id)
} else {
coroutineScope.launch {
runCatching {
LibraryRepository.removeFromList(target.item, listKey)
}.onFailure { error ->
NuvioToastController.show(
error.message ?: getString(Res.string.trakt_lists_update_failed),
)
}
}
}
}
},
onDismiss = { pendingRemovalTarget = null },
)
} }
private fun LazyListScope.librarySections( private fun LazyListScope.librarySections(
sections: List<LibrarySection>, sections: List<LibrarySection>,
onPosterClick: ((LibraryItem) -> Unit)?, onPosterClick: ((LibraryItem) -> Unit)?,
onSectionViewAllClick: ((LibrarySection) -> Unit)?, onSectionViewAllClick: ((LibrarySection) -> Unit)?,
onPosterLongClick: (LibraryItem, LibrarySection) -> Unit, onPosterLongClick: ((LibraryItem, LibrarySection) -> Unit)?,
) { ) {
items( items(
items = sections, items = sections,
@ -267,7 +212,7 @@ private fun LazyListScope.librarySections(
HomePosterCard( HomePosterCard(
item = item.toMetaPreview(), item = item.toMetaPreview(),
onClick = onPosterClick?.let { { it(item) } }, onClick = onPosterClick?.let { { it(item) } },
onLongClick = { onPosterLongClick(item, section) }, onLongClick = onPosterLongClick?.let { { it(item, section) } },
) )
} }
} }