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.LibraryScreen
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.player.PlayerLaunch
import com.nuvio.app.features.player.PlayerLaunchStore
@ -276,6 +277,12 @@ data class CatalogRoute(
val genre: String? = null,
)
private data class PosterActionTarget(
val preview: MetaPreview,
val libraryItem: LibraryItem? = null,
val libraryListKey: String? = null,
)
enum class AppScreenTab {
Home,
Search,
@ -556,7 +563,7 @@ private fun MainAppContent(
}.collectAsStateWithLifecycle()
val liquidGlassNativeTabBarSupported = remember { isLiquidGlassNativeTabBarSupported() }
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 showLibraryListPicker by remember { mutableStateOf(false) }
var pickerItem by remember { mutableStateOf<LibraryItem?>(null) }
@ -1136,11 +1143,19 @@ private fun MainAppContent(
},
onPosterLongClick = { meta ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterForActions = meta
selectedPosterActionTarget = PosterActionTarget(preview = meta)
},
onLibraryPosterClick = { item ->
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,
onContinueWatchingClick = onContinueWatchingClick,
onContinueWatchingLongPress = onContinueWatchingLongPress,
@ -1832,6 +1847,18 @@ private fun MainAppContent(
onPosterClick = { meta ->
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(),
)
}
@ -1997,48 +2024,74 @@ private fun MainAppContent(
}
NuvioPosterActionSheet(
item = selectedPosterForActions,
isSaved = selectedPosterForActions?.let { preview ->
item = selectedPosterActionTarget?.preview,
isSaved = selectedPosterActionTarget?.preview?.let { preview ->
LibraryRepository.isSaved(preview.id, preview.type)
} == true,
isWatched = selectedPosterForActions?.let { preview ->
isWatched = selectedPosterActionTarget?.preview?.let { preview ->
WatchingState.isPosterWatched(
watchedKeys = watchedUiState.watchedKeys,
item = preview,
)
} == true,
onDismiss = { selectedPosterForActions = null },
onDismiss = { selectedPosterActionTarget = null },
onToggleLibrary = {
selectedPosterForActions?.let { preview ->
val libraryItem = preview.toLibraryItem(savedAtEpochMs = 0L)
if (!isTraktLibrarySource) {
LibraryRepository.toggleSaved(libraryItem)
} else {
pickerItem = libraryItem
pickerTitle = preview.name
pickerTabs = LibraryRepository.libraryListTabs()
pickerMembership = pickerTabs.associate { it.key to false }
pickerPending = true
pickerError = null
showLibraryListPicker = true
coroutineScope.launch {
runCatching {
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
val tabs = LibraryRepository.libraryListTabs()
pickerTabs = tabs
pickerMembership = tabs.associate { tab ->
tab.key to (snapshot[tab.key] == true)
selectedPosterActionTarget?.let { target ->
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),
)
}
}.onFailure { error ->
pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed)
}
pickerPending = false
} else {
LibraryRepository.remove(libraryItem.id)
}
} else {
if (!isTraktLibrarySource) {
LibraryRepository.toggleSaved(libraryItem)
} else {
pickerItem = libraryItem
pickerTitle = preview.name
pickerTabs = LibraryRepository.libraryListTabs()
pickerMembership = pickerTabs.associate { it.key to false }
pickerPending = true
pickerError = null
showLibraryListPicker = true
coroutineScope.launch {
runCatching {
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
val tabs = LibraryRepository.libraryListTabs()
pickerTabs = tabs
pickerMembership = tabs.associate { tab ->
tab.key to (snapshot[tab.key] == true)
}
}.onFailure { error ->
pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed)
}
pickerPending = false
}
}
}
}
},
onToggleWatched = {
selectedPosterForActions?.let { preview ->
selectedPosterActionTarget?.preview?.let { preview ->
coroutineScope.launch {
WatchingActions.togglePosterWatched(preview)
}
@ -2223,6 +2276,7 @@ private fun AppTabHost(
onPosterClick: ((MetaPreview) -> Unit)? = null,
onPosterLongClick: ((MetaPreview) -> Unit)? = null,
onLibraryPosterClick: ((LibraryItem) -> Unit)? = null,
onLibraryPosterLongClick: ((LibraryItem, LibrarySection) -> Unit)? = null,
onLibrarySectionViewAllClick: ((LibrarySection) -> Unit)? = null,
onContinueWatchingClick: ((ContinueWatchingItem) -> Unit)? = null,
onContinueWatchingLongPress: ((ContinueWatchingItem) -> Unit)? = null,
@ -2276,6 +2330,7 @@ private fun AppTabHost(
modifier = Modifier.fillMaxSize(),
scrollToTopRequests = libraryScrollToTopRequests,
onPosterClick = onLibraryPosterClick,
onPosterLongClick = onLibraryPosterLongClick,
onSectionViewAllClick = onLibrarySectionViewAllClick,
)
}

View file

@ -72,6 +72,7 @@ fun CatalogScreen(
genre: String? = null,
onBack: () -> Unit,
onPosterClick: ((MetaPreview) -> Unit)? = null,
onPosterLongClick: ((MetaPreview) -> Unit)? = null,
modifier: Modifier = Modifier,
) {
val uiState by CatalogRepository.uiState.collectAsStateWithLifecycle()
@ -187,6 +188,7 @@ fun CatalogScreen(
cornerRadiusDp = posterCardStyle.cornerRadiusDp,
hideLabels = posterCardStyle.hideLabelsEnabled,
onClick = onPosterClick?.let { { it(item) } },
onLongClick = onPosterLongClick?.let { { it(item) } },
)
}
if (uiState.isLoading) {
@ -257,6 +259,7 @@ private fun CatalogPosterTile(
cornerRadiusDp: Int,
hideLabels: Boolean,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
@ -267,7 +270,7 @@ private fun CatalogPosterTile(
.aspectRatio(item.posterShape.catalogAspectRatio())
.clip(RoundedCornerShape(cornerRadiusDp.dp))
.background(MaterialTheme.colorScheme.surface)
.posterCardClickable(onClick = onClick, onLongClick = null),
.posterCardClickable(onClick = onClick, onLongClick = onLongClick),
) {
if (item.poster != null) {
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.NuvioNetworkOfflineCard
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.NuvioShelfSection
import com.nuvio.app.core.ui.nuvioBlockPointerPassthrough
@ -38,20 +36,14 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import nuvio.composeapp.generated.resources.*
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
private data class LibraryRemovalTarget(
val item: LibraryItem,
val listKey: String? = null,
val listTitle: String? = null,
)
@Composable
fun LibraryScreen(
modifier: Modifier = Modifier,
scrollToTopRequests: Flow<Unit> = emptyFlow(),
onPosterClick: ((LibraryItem) -> Unit)? = null,
onPosterLongClick: ((LibraryItem, LibrarySection) -> Unit)? = null,
onSectionViewAllClick: ((LibrarySection) -> Unit)? = null,
) {
val uiState by remember {
@ -59,7 +51,6 @@ fun LibraryScreen(
LibraryRepository.uiState
}.collectAsStateWithLifecycle()
val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle()
var pendingRemovalTarget by remember { mutableStateOf<LibraryRemovalTarget?>(null) }
var observedOfflineState by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState()
@ -187,64 +178,18 @@ fun LibraryScreen(
sections = uiState.sections,
onPosterClick = onPosterClick,
onSectionViewAllClick = onSectionViewAllClick,
onPosterLongClick = { item, section ->
pendingRemovalTarget = if (isTraktSource) {
LibraryRemovalTarget(
item = item,
listKey = section.type,
listTitle = section.displayTitle,
)
} else {
LibraryRemovalTarget(item = item)
}
},
onPosterLongClick = onPosterLongClick,
)
}
}
}
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(
sections: List<LibrarySection>,
onPosterClick: ((LibraryItem) -> Unit)?,
onSectionViewAllClick: ((LibrarySection) -> Unit)?,
onPosterLongClick: (LibraryItem, LibrarySection) -> Unit,
onPosterLongClick: ((LibraryItem, LibrarySection) -> Unit)?,
) {
items(
items = sections,
@ -267,7 +212,7 @@ private fun LazyListScope.librarySections(
HomePosterCard(
item = item.toMetaPreview(),
onClick = onPosterClick?.let { { it(item) } },
onLongClick = { onPosterLongClick(item, section) },
onLongClick = onPosterLongClick?.let { { it(item, section) } },
)
}
}