mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-19 00:11:45 +00:00
feat: remaining places to use bottomsheetmodal
This commit is contained in:
parent
7a5c531a24
commit
f8eb52c299
3 changed files with 92 additions and 89 deletions
|
|
@ -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,48 +2024,74 @@ 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
|
||||||
if (!isTraktLibrarySource) {
|
val libraryItem = target.libraryItem ?: preview.toLibraryItem(savedAtEpochMs = 0L)
|
||||||
LibraryRepository.toggleSaved(libraryItem)
|
if (target.libraryItem != null) {
|
||||||
} else {
|
if (isTraktLibrarySource) {
|
||||||
pickerItem = libraryItem
|
coroutineScope.launch {
|
||||||
pickerTitle = preview.name
|
runCatching {
|
||||||
pickerTabs = LibraryRepository.libraryListTabs()
|
val listKey = target.libraryListKey
|
||||||
pickerMembership = pickerTabs.associate { it.key to false }
|
if (listKey.isNullOrBlank()) {
|
||||||
pickerPending = true
|
val currentMembership = LibraryRepository.getMembershipSnapshot(libraryItem)
|
||||||
pickerError = null
|
LibraryRepository.applyMembershipChanges(
|
||||||
showLibraryListPicker = true
|
item = libraryItem,
|
||||||
coroutineScope.launch {
|
desiredMembership = currentMembership.mapValues { false },
|
||||||
runCatching {
|
)
|
||||||
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
|
} else {
|
||||||
val tabs = LibraryRepository.libraryListTabs()
|
LibraryRepository.removeFromList(libraryItem, listKey)
|
||||||
pickerTabs = tabs
|
}
|
||||||
pickerMembership = tabs.associate { tab ->
|
}.onFailure { error ->
|
||||||
tab.key to (snapshot[tab.key] == true)
|
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 = {
|
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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue