Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal data class ResolvedMedia(
val videoNumber: Int?,
val episodeId: Int?,
val episodeTitle: String?,
val isCurrentMediaWatched: Boolean,
val isSeries: Boolean,
val hasNext: Boolean,
val hasPrevious: Boolean,
Expand Down Expand Up @@ -70,6 +71,7 @@ internal class PlayerInteractor(
videoNumber = episode?.number,
episodeId = episode?.id,
episodeTitle = episode?.title,
isCurrentMediaWatched = isWatched(episode?.watched),
isSeries = true,
hasNext = resolvedSeason != null && resolvedEpisode != null &&
findNextEpisode(item, resolvedSeason, resolvedEpisode) != null,
Expand All @@ -89,6 +91,7 @@ internal class PlayerInteractor(
videoNumber = video?.number,
episodeId = null,
episodeTitle = null,
isCurrentMediaWatched = isWatched(item.watched),
isSeries = false,
hasNext = false,
hasPrevious = false,
Expand All @@ -103,6 +106,8 @@ internal class PlayerInteractor(
else -> false
}

private fun isWatched(watched: Int?): Boolean = watched == WATCHED_STATUS

fun findEpisode(item: Item, seasonNumber: Int, episodeNumber: Int) =
item.seasons
?.find { it.number == seasonNumber }
Expand Down Expand Up @@ -223,6 +228,16 @@ internal class PlayerInteractor(
api.toggleWatchingStatus(id, status = WATCHED_STATUS, season = season, video = videoNumber)
}

suspend fun markCurrentAsWatched(id: Int, season: Int? = null, episode: Int? = null): Item {
api.toggleWatchingStatus(
id = id,
status = WATCHED_STATUS,
season = season,
video = episode,
).getOrThrow()
return itemDetailsRepository.refresh(id)
}

suspend fun setEpisodeWatched(id: Int, season: Int, episode: Int, watched: Boolean): Item {
api.toggleWatchingStatus(
id = id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import androidx.tv.material3.ButtonDefaults
import androidx.tv.material3.Icon
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Duotone
import com.adamglin.phosphoricons.Fill
import com.adamglin.phosphoricons.duotone.Eye
import com.adamglin.phosphoricons.fill.Eye
import com.kino.puber.R

@Composable
Expand Down Expand Up @@ -58,6 +63,8 @@ internal data class PlayerButtonRowState(
val isPlaying: Boolean,
val hasNextEpisode: Boolean,
val hasPreviousEpisode: Boolean,
val canMarkCurrentWatched: Boolean,
val isCurrentMediaWatched: Boolean,
)

@Composable
Expand All @@ -72,6 +79,19 @@ private fun PrimaryControls(
onClick = actions.onTogglePlayPause,
focusRequester = focusRequesters.firstButton,
)
if (state.canMarkCurrentWatched) {
PlayerButton(
text = stringResource(R.string.player_button_mark_watched),
icon = if (state.isCurrentMediaWatched) {
PhosphorIcons.Fill.Eye
} else {
PhosphorIcons.Duotone.Eye
},
onClick = actions.onMarkCurrentWatchedClick,
selected = state.isCurrentMediaWatched,
contentDescription = stringResource(R.string.player_button_mark_watched_content_description),
)
}
if (!state.isMovie) {
PlayerButton(
text = stringResource(R.string.player_button_episodes),
Expand Down Expand Up @@ -154,15 +174,17 @@ private fun PlayerButton(
icon: ImageVector,
onClick: () -> Unit,
modifier: Modifier = Modifier,
selected: Boolean = false,
contentDescription: String? = null,
) {
Button(
onClick = onClick,
modifier = modifier,
colors = transparentButtonColors(),
colors = if (selected) selectedButtonColors() else transparentButtonColors(),
) {
Icon(
imageVector = icon,
contentDescription = null,
contentDescription = contentDescription,
modifier = Modifier.size(20.dp),
)
Spacer(modifier = Modifier.width(8.dp))
Expand All @@ -180,3 +202,11 @@ private fun transparentButtonColors() = ButtonDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.primary,
focusedContentColor = MaterialTheme.colorScheme.onPrimary,
)

@Composable
private fun selectedButtonColors() = ButtonDefaults.colors(
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.primary,
focusedContainerColor = MaterialTheme.colorScheme.primary,
focusedContentColor = MaterialTheme.colorScheme.onPrimary,
)
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ internal fun PlayerControlsOverlay(
isPlaying: Boolean,
hasNextEpisode: Boolean,
hasPreviousEpisode: Boolean,
canMarkCurrentWatched: Boolean,
isCurrentMediaWatched: Boolean,
onEpisodesClick: () -> Unit,
onMarkCurrentWatchedClick: () -> Unit,
onAudioSubtitlesClick: () -> Unit,
onVideoSettingsClick: () -> Unit,
onNextEpisodeClick: () -> Unit,
Expand Down Expand Up @@ -67,9 +70,12 @@ internal fun PlayerControlsOverlay(
isPlaying = isPlaying,
hasNextEpisode = hasNextEpisode,
hasPreviousEpisode = hasPreviousEpisode,
canMarkCurrentWatched = canMarkCurrentWatched,
isCurrentMediaWatched = isCurrentMediaWatched,
),
actions = PlayerControlActions(
onEpisodesClick = onEpisodesClick,
onMarkCurrentWatchedClick = onMarkCurrentWatchedClick,
onAudioSubtitlesClick = onAudioSubtitlesClick,
onVideoSettingsClick = onVideoSettingsClick,
onNextEpisodeClick = onNextEpisodeClick,
Expand Down Expand Up @@ -99,6 +105,7 @@ private data class ProgressBarState(

internal data class PlayerControlActions(
val onEpisodesClick: () -> Unit,
val onMarkCurrentWatchedClick: () -> Unit,
val onAudioSubtitlesClick: () -> Unit,
val onVideoSettingsClick: () -> Unit,
val onNextEpisodeClick: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ internal fun PlayerControlsLayer(
isPlaying = content.isPlaying,
hasNextEpisode = content.hasNextEpisode,
hasPreviousEpisode = content.hasPreviousEpisode,
canMarkCurrentWatched = content.canMarkCurrentWatched,
isCurrentMediaWatched = content.isCurrentMediaWatched,
onEpisodesClick = rememberAction(onAction, PlayerAction.OpenEpisodesPanel),
onMarkCurrentWatchedClick = rememberAction(onAction, PlayerAction.MarkCurrentWatched),
onAudioSubtitlesClick = rememberAction(onAction, PlayerAction.OpenAudioSubtitlesPanel),
onVideoSettingsClick = rememberAction(onAction, PlayerAction.OpenVideoSettingsPanel),
onNextEpisodeClick = rememberAction(onAction, PlayerAction.NextEpisode),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ private fun previewSeriesContent(
seekIndicator: SeekIndicatorState? = null,
nextEpisodeCountdown: Int? = null,
resumeDialog: ResumeDialogState? = null,
canMarkCurrentWatched: Boolean = true,
isCurrentMediaWatched: Boolean = false,
) = PlayerContentState(
title = "Кибердеревня",
subtitle = "2 сезон, 6 серия, Что там, за железным занавесом?",
Expand Down Expand Up @@ -122,6 +124,8 @@ private fun previewSeriesContent(
isMovie = false,
hasNextEpisode = true,
hasPreviousEpisode = true,
canMarkCurrentWatched = canMarkCurrentWatched,
isCurrentMediaWatched = isCurrentMediaWatched,
nextEpisodeCountdown = nextEpisodeCountdown,
resumeDialog = resumeDialog,
episodes = previewEpisodes,
Expand All @@ -132,6 +136,8 @@ private fun previewMovieContent(
controlsVisible: Boolean = true,
activePanel: ActivePanel = ActivePanel.None,
resumeDialog: ResumeDialogState? = null,
canMarkCurrentWatched: Boolean = true,
isCurrentMediaWatched: Boolean = false,
) = PlayerContentState(
title = "Граф Монте-Кристо",
subtitle = null,
Expand Down Expand Up @@ -162,6 +168,8 @@ private fun previewMovieContent(
isMovie = true,
hasNextEpisode = false,
hasPreviousEpisode = false,
canMarkCurrentWatched = canMarkCurrentWatched,
isCurrentMediaWatched = isCurrentMediaWatched,
nextEpisodeCountdown = null,
resumeDialog = resumeDialog,
episodes = null,
Expand Down Expand Up @@ -212,6 +220,16 @@ private fun MovieControlsPreview() = PuberTheme {
)
}

@Preview(name = "Movie — watched control active", device = TV_1080p)
@Composable
private fun MovieWatchedControlsPreview() = PuberTheme {
PlayerScreenContent(
state = PlayerViewState.Content(previewMovieContent(isCurrentMediaWatched = true)),
onAction = {},
exoPlayer = { null },
)
}

@Preview(name = "Series — controls hidden + seek forward", device = TV_1080p)
@Composable
private fun SeekForwardPreview() = PuberTheme {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal sealed class PlayerAction : UIAction {
data class SelectEpisodeById(val episodeId: Int) : PlayerAction()
data class EpisodeWatchedChanged(val item: VideoItemUIState, val watched: Boolean) : PlayerAction()
data class SeasonWatchedChanged(val item: VideoItemUIState, val watched: Boolean) : PlayerAction()
data object MarkCurrentWatched : PlayerAction()
data object NextEpisode : PlayerAction()
data object PreviousEpisode : PlayerAction()
data object CancelNextEpisodeCountdown : PlayerAction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ internal data class PlayerContentState(
val isMovie: Boolean,
val hasNextEpisode: Boolean,
val hasPreviousEpisode: Boolean,
val canMarkCurrentWatched: Boolean,
val isCurrentMediaWatched: Boolean,

// Next episode countdown
val nextEpisodeCountdown: Int?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ internal class ContentStateFactory(private val mapper: PlayerUIMapper) {
isMovie = !resolved.isSeries,
hasNextEpisode = resolved.hasNext,
hasPreviousEpisode = resolved.hasPrevious,
canMarkCurrentWatched = !resolved.isSeries ||
(resolved.seasonNumber != null && resolved.episodeNumber != null),
isCurrentMediaWatched = resolved.isCurrentMediaWatched,
nextEpisodeCountdown = null,
resumeDialog = resumeDialog,
episodes = if (resolved.isSeries) mapper.mapEpisodes(item) else null,
Expand Down
Loading
Loading