mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-24 03:36:51 +00:00
feat(series): add long-click context menu to episode cards
Add a ModalBottomSheet with Mark as watched/unwatched actions on long-click of episode cards. - Replace clickable with combinedClickable to support long-click gestures - Add markEpisodeAsWatched function to SeriesViewModel - Add MediaAction model usage for bottom sheet actions - Add test tag for the dialog component
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
package hu.bbara.purefin.ui.screen.series.components
|
package hu.bbara.purefin.ui.screen.series.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -60,6 +62,7 @@ import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
|||||||
import hu.bbara.purefin.ui.common.bar.MediaProgressBar
|
import hu.bbara.purefin.ui.common.bar.MediaProgressBar
|
||||||
import hu.bbara.purefin.ui.common.button.GhostIconButton
|
import hu.bbara.purefin.ui.common.button.GhostIconButton
|
||||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||||
|
import hu.bbara.purefin.ui.model.MediaAction
|
||||||
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
|
import hu.bbara.purefin.ui.screen.home.components.DefaultTopBar
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -315,6 +318,7 @@ internal fun EpisodeCarousel(episodes: List<Episode>, modifier: Modifier = Modif
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun EpisodeCard(
|
private fun EpisodeCard(
|
||||||
viewModel: SeriesViewModel = hiltViewModel(),
|
viewModel: SeriesViewModel = hiltViewModel(),
|
||||||
@@ -322,15 +326,31 @@ private fun EpisodeCard(
|
|||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||||
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
|
val popupActions = remember(episode.id) {
|
||||||
|
listOf(
|
||||||
|
MediaAction(name = "Mark as watched") {
|
||||||
|
viewModel.markEpisodeAsWatched(episode.id, true)
|
||||||
|
},
|
||||||
|
MediaAction(name = "Mark as unwatched") {
|
||||||
|
viewModel.markEpisodeAsWatched(episode.id, false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.testTag("$SeriesEpisodeCardTagPrefix${episode.id}")
|
.testTag("$SeriesEpisodeCardTagPrefix${episode.id}")
|
||||||
.clickable { viewModel.onSelectEpisode(
|
.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
viewModel.onSelectEpisode(
|
||||||
seriesId = episode.seriesId,
|
seriesId = episode.seriesId,
|
||||||
seasonId = episode.seasonId,
|
seasonId = episode.seasonId,
|
||||||
episodeId = episode.id
|
episodeId = episode.id
|
||||||
) },
|
)
|
||||||
|
},
|
||||||
|
onLongClick = { showBottomSheet = true }
|
||||||
|
),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -389,6 +409,30 @@ private fun EpisodeCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showBottomSheet) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = { showBottomSheet = false },
|
||||||
|
modifier = Modifier.testTag(SeriesEpisodeActionsDialogTag),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 24.dp)
|
||||||
|
) {
|
||||||
|
popupActions.forEach { action ->
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(text = action.name) },
|
||||||
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
action.onClick()
|
||||||
|
showBottomSheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val SeriesPlayButtonTag = "series-play-button"
|
internal const val SeriesPlayButtonTag = "series-play-button"
|
||||||
@@ -402,6 +446,7 @@ internal const val SeriesSmartDownloadButtonTag = "series-smart-download-button"
|
|||||||
internal const val SeriesSeasonSelectorTag = "series-season-selector"
|
internal const val SeriesSeasonSelectorTag = "series-season-selector"
|
||||||
internal const val SeriesEpisodeCarouselTag = "series-episode-carousel"
|
internal const val SeriesEpisodeCarouselTag = "series-episode-carousel"
|
||||||
internal const val SeriesEpisodeCardTagPrefix = "series-episode-card-"
|
internal const val SeriesEpisodeCardTagPrefix = "series-episode-card-"
|
||||||
|
internal const val SeriesEpisodeActionsDialogTag = "series-episode-actions-dialog"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CastRow(cast: List<CastMember>, modifier: Modifier = Modifier) {
|
internal fun CastRow(cast: List<CastMember>, modifier: Modifier = Modifier) {
|
||||||
|
|||||||
@@ -186,6 +186,12 @@ class SeriesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun markEpisodeAsWatched(episodeId: UUID, watched: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
mediaMetadataUpdater.markAsWatched(episodeId, watched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun selectSeries(series: SeriesDto) {
|
fun selectSeries(series: SeriesDto) {
|
||||||
_series.value = series
|
_series.value = series
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|||||||
Reference in New Issue
Block a user