Tighten TV back navigation, continue-watching actions, and detail focus.

Long-press continue items to mark watched or hide, scroll-back before exit, confirm quit on home, and focus Play by default.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jos Vooges | STH 2026-09-01 00:25:43 +02:00
parent 7e624dd452
commit 04d6a38234
7 changed files with 371 additions and 158 deletions

View file

@ -12,8 +12,8 @@ android {
applicationId = "nl.vonas.mediacluster.tv"
minSdk = 24
targetSdk = 35
versionCode = 12
versionName = "0.7.5"
versionCode = 13
versionName = "0.7.6"
buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"")
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a")

View file

@ -3,7 +3,6 @@ package nl.vonas.mediacluster.tv.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@ -47,6 +46,7 @@ fun BrowseScreen(
catalogId: String,
titleHint: String? = null,
onBack: (() -> Unit)? = null,
onAtTopBack: (() -> Unit)? = null,
onOpenMovie: (String) -> Unit,
onOpenSeries: (String) -> Unit,
onPlayContinue: ((
@ -109,8 +109,19 @@ fun BrowseScreen(
}
}
if (onBack != null) {
BackHandler(onBack = onBack)
val handleBack = onBack != null || onAtTopBack != null
if (handleBack) {
BackHandler {
val scrolled = gridState.firstVisibleItemIndex > 0 ||
gridState.firstVisibleItemScrollOffset > 0
if (scrolled) {
scope.launch { gridState.animateScrollToItem(0) }
} else if (onBack != null) {
onBack()
} else {
onAtTopBack?.invoke()
}
}
}
Column(

View file

@ -1,5 +1,6 @@
package nl.vonas.mediacluster.tv.ui
import android.app.Activity
import android.os.Build
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
@ -19,6 +20,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -202,7 +204,11 @@ private fun MainHub(
) {
var tab by remember { mutableStateOf(MainTab.Home) }
var navFocused by remember { mutableStateOf(false) }
var exitConfirm by remember { mutableStateOf(false) }
val navFocus = remember { FocusRequester() }
val homeListState = rememberLazyListState()
val hubScope = rememberCoroutineScope()
val activity = LocalContext.current as? Activity
var shelfPrefs by remember { mutableStateOf<List<ShelfPref>>(emptyList()) }
var movieCatalog by remember { mutableStateOf("movies") }
var seriesCatalog by remember { mutableStateOf("series") }
@ -252,13 +258,40 @@ private fun MainHub(
else -> null
}
BackHandler(enabled = !navFocused && tab != MainTab.Home) {
tab = MainTab.Home
fun focusTopNav() {
navFocused = true
exitConfirm = false
hubScope.launch {
delay(40)
runCatching { navFocus.requestFocus() }
}
}
BackHandler(enabled = !navFocused && tab == MainTab.Home) {
navFocused = true
runCatching { navFocus.requestFocus() }
// Exit-dialog: terug annuleert
BackHandler(enabled = exitConfirm) {
exitConfirm = false
}
// Home + top-nav: vraag om afsluiten
BackHandler(enabled = tab == MainTab.Home && navFocused && !exitConfirm) {
exitConfirm = true
}
// Home content: eerst scroll omhoog, anders top-nav
BackHandler(enabled = tab == MainTab.Home && !navFocused && !exitConfirm) {
if (homeListState.firstVisibleItemIndex > 0 || homeListState.firstVisibleItemScrollOffset > 0) {
hubScope.launch { homeListState.animateScrollToItem(0) }
} else {
focusTopNav()
}
}
// Films/Series/Zoeken: BrowseScreen handelt scroll→top af; hier alleen top-nav → Home
BackHandler(enabled = navFocused && tab != MainTab.Home && !exitConfirm) {
tab = MainTab.Home
focusTopNav()
}
// Settings (geen eigen scroll-back): terug naar home
BackHandler(enabled = tab == MainTab.Settings && !navFocused && !exitConfirm) {
tab = MainTab.Home
focusTopNav()
}
LaunchedEffect(navFocused) {
@ -268,83 +301,124 @@ private fun MainHub(
}
}
Column(Modifier.fillMaxSize().background(McColors.Bg)) {
Box(Modifier.focusRequester(navFocus)) {
TopNavBar(
selected = tab,
onSelect = {
tab = it
navFocused = true
when (it) {
MainTab.Movies -> {
if (movieSubId == "__search__") {
movieSubId = "movies"
movieCatalog = "movies"
}
}
MainTab.Series -> {
if (seriesSubId == "__search__") {
seriesSubId = "series"
seriesCatalog = "series"
}
}
else -> Unit
}
},
subItems = subItems,
selectedSubId = selectedSubId,
onSelectSub = { item ->
if (item.id == "__search__") {
tab = MainTab.Search
navFocused = true
} else {
when (tab) {
Box(Modifier.fillMaxSize().background(McColors.Bg)) {
Column(Modifier.fillMaxSize()) {
Box(Modifier.focusRequester(navFocus)) {
TopNavBar(
selected = tab,
onSelect = {
tab = it
exitConfirm = false
focusTopNav()
when (it) {
MainTab.Movies -> {
movieSubId = item.id
movieCatalog = item.catalogId
if (movieSubId == "__search__") {
movieSubId = "movies"
movieCatalog = "movies"
}
}
MainTab.Series -> {
seriesSubId = item.id
seriesCatalog = item.catalogId
if (seriesSubId == "__search__") {
seriesSubId = "series"
seriesCatalog = "series"
}
}
else -> Unit
}
}
},
)
},
subItems = subItems,
selectedSubId = selectedSubId,
onSelectSub = { item ->
if (item.id == "__search__") {
tab = MainTab.Search
focusTopNav()
} else {
when (tab) {
MainTab.Movies -> {
movieSubId = item.id
movieCatalog = item.catalogId
}
MainTab.Series -> {
seriesSubId = item.id
seriesCatalog = item.catalogId
}
else -> Unit
}
}
},
)
}
when (tab) {
MainTab.Home -> HomeScreen(
api = api,
listState = homeListState,
onContentFocused = { navFocused = false },
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
onSeeAll = onBrowse,
onPlay = onPlay,
)
MainTab.Movies -> BrowseScreen(
api = api,
catalogId = movieCatalog,
titleHint = movieSubs.firstOrNull { it.id == movieSubId }?.label ?: "Films",
onAtTopBack = { focusTopNav() },
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
)
MainTab.Series -> BrowseScreen(
api = api,
catalogId = seriesCatalog,
titleHint = seriesSubs.firstOrNull { it.id == seriesSubId }?.label ?: "Series",
onAtTopBack = { focusTopNav() },
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
)
MainTab.Search -> SearchScreen(
api = api,
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
onAtTopBack = { focusTopNav() },
)
MainTab.Settings -> SettingsScreen(
api = api,
onBack = {
tab = MainTab.Home
focusTopNav()
},
onLogout = onLogout,
)
}
}
when (tab) {
MainTab.Home -> HomeScreen(
api = api,
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
onSeeAll = onBrowse,
onPlay = onPlay,
)
MainTab.Movies -> BrowseScreen(
api = api,
catalogId = movieCatalog,
titleHint = movieSubs.firstOrNull { it.id == movieSubId }?.label ?: "Films",
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
)
MainTab.Series -> BrowseScreen(
api = api,
catalogId = seriesCatalog,
titleHint = seriesSubs.firstOrNull { it.id == seriesSubId }?.label ?: "Series",
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
)
MainTab.Search -> SearchScreen(
api = api,
onOpenMovie = onOpenMovie,
onOpenSeries = onOpenSeries,
)
MainTab.Settings -> SettingsScreen(
api = api,
onBack = { tab = MainTab.Home },
onLogout = onLogout,
)
if (exitConfirm) {
Box(
Modifier
.fillMaxSize()
.background(Color(0xCC000000)),
contentAlignment = Alignment.Center,
) {
Column(
Modifier
.width(420.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF1A1A1A))
.padding(28.dp),
verticalArrangement = Arrangement.spacedBy(14.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text("VMC afsluiten?", color = Color.White, fontSize = 22.sp, fontWeight = FontWeight.Bold)
Text(
"Weet je zeker dat je de app wilt sluiten?",
color = McColors.TextDim,
fontSize = 15.sp,
)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
McButton("Annuleren") { exitConfirm = false }
McButton("Afsluiten", primary = true) { activity?.finish() }
}
}
}
}
}
}
@ -437,6 +511,8 @@ private fun LinkScreen(api: ApiClient, store: SessionStore, onLinked: () -> Unit
@Composable
private fun HomeScreen(
api: ApiClient,
listState: androidx.compose.foundation.lazy.LazyListState,
onContentFocused: () -> Unit,
onOpenMovie: (String) -> Unit,
onOpenSeries: (String) -> Unit,
onSeeAll: (catalogId: String, title: String) -> Unit,
@ -444,92 +520,170 @@ private fun HomeScreen(
) {
var home by remember { mutableStateOf<HomePayload?>(null) }
var error by remember { mutableStateOf<String?>(null) }
var continueMenu by remember { mutableStateOf<MediaItem?>(null) }
val scope = rememberCoroutineScope()
val menuFocus = remember { FocusRequester() }
suspend fun reloadHome() {
home = api.home()
}
LaunchedEffect(Unit) {
try {
home = api.home()
reloadHome()
} catch (e: Exception) {
error = e.message
}
}
LaunchedEffect(continueMenu) {
if (continueMenu != null) {
delay(40)
runCatching { menuFocus.requestFocus() }
}
}
val spotlight = home?.spotlight.orEmpty().ifEmpty {
home?.shelves?.flatMap { it.items }?.distinctBy { "${it.type}:${it.id}" }?.take(10).orEmpty()
}
LazyColumn(
Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 56.dp),
verticalArrangement = Arrangement.spacedBy(28.dp),
) {
item {
SpotlightBillboard(
items = spotlight,
onPlay = { item ->
when {
!item.mediaFileId.isNullOrBlank() -> onPlay(
item.mediaFileId!!,
item.id.takeIf { item.type == "movie" },
item.id.takeIf { item.type == "episode" },
null,
if (item.type == "episode") "EPISODE" else "MOVIE",
item.positionMs,
)
item.type == "movie" -> onOpenMovie(item.id)
else -> onOpenSeries(item.id)
}
},
onOpen = { item ->
if (item.type == "movie") onOpenMovie(item.id) else onOpenSeries(item.id)
},
)
}
if (error != null) {
item { Text(error!!, color = McColors.Danger, modifier = Modifier.padding(horizontal = 40.dp)) }
}
val continueItems = home?.continueWatching.orEmpty()
if (continueItems.isNotEmpty()) {
val preview = continueItems.take(5)
Box(Modifier.fillMaxSize()) {
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 56.dp),
verticalArrangement = Arrangement.spacedBy(28.dp),
) {
item {
ShelfBlock(
title = "Verder kijken",
items = preview,
onSeeAll = if (continueItems.size > 5) {
{ onSeeAll("continue-watching", "Verder kijken") }
} else {
null
},
onClick = { item ->
SpotlightBillboard(
items = spotlight,
onPlay = { item ->
onContentFocused()
when {
!item.mediaFileId.isNullOrBlank() -> onPlay(
item.mediaFileId!!,
item.id.takeIf { item.type == "movie" },
item.id.takeIf { item.type == "episode" },
item.seriesId,
null,
if (item.type == "episode") "EPISODE" else "MOVIE",
item.positionMs,
)
item.type == "movie" -> onOpenMovie(item.id)
item.type == "episode" && !item.seriesId.isNullOrBlank() -> onOpenSeries(item.seriesId!!)
else -> onOpenSeries(item.id)
}
},
onOpen = { item ->
onContentFocused()
if (item.type == "movie") onOpenMovie(item.id) else onOpenSeries(item.id)
},
)
}
if (error != null) {
item { Text(error!!, color = McColors.Danger, modifier = Modifier.padding(horizontal = 40.dp)) }
}
val continueItems = home?.continueWatching.orEmpty()
if (continueItems.isNotEmpty()) {
val preview = continueItems.take(5)
item {
ShelfBlock(
title = "Verder kijken",
items = preview,
onSeeAll = if (continueItems.size > 5) {
{ onSeeAll("continue-watching", "Verder kijken") }
} else {
null
},
onClick = { item ->
onContentFocused()
when {
!item.mediaFileId.isNullOrBlank() -> onPlay(
item.mediaFileId!!,
item.id.takeIf { item.type == "movie" },
item.id.takeIf { item.type == "episode" },
item.seriesId,
if (item.type == "episode") "EPISODE" else "MOVIE",
item.positionMs,
)
item.type == "movie" -> onOpenMovie(item.id)
item.type == "episode" && !item.seriesId.isNullOrBlank() -> onOpenSeries(item.seriesId!!)
else -> onOpenSeries(item.id)
}
},
onLongClick = { item -> continueMenu = item },
)
}
}
items(home?.shelves.orEmpty()) { shelf ->
val catalogId = when (shelf.slug) {
"recent-movies", "recent-series", "4k-movies" -> shelf.slug
else -> shelf.slug.ifBlank { shelf.id }
}
ShelfBlock(
title = shelf.title,
items = shelf.items,
onSeeAll = { onSeeAll(catalogId, shelf.title) },
onClick = { item ->
onContentFocused()
if (item.type == "movie") onOpenMovie(item.id) else onOpenSeries(item.id)
},
)
}
}
items(home?.shelves.orEmpty()) { shelf ->
val catalogId = when (shelf.slug) {
"recent-movies", "recent-series", "4k-movies" -> shelf.slug
else -> shelf.slug.ifBlank { shelf.id }
val menuItem = continueMenu
if (menuItem != null) {
BackHandler { continueMenu = null }
Box(
Modifier
.fillMaxSize()
.background(Color(0xCC000000)),
contentAlignment = Alignment.Center,
) {
Column(
Modifier
.width(440.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF1A1A1A))
.padding(28.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Text(menuItem.title, color = Color.White, fontSize = 20.sp, fontWeight = FontWeight.Bold, maxLines = 2)
if (!menuItem.subtitle.isNullOrBlank()) {
Text(menuItem.subtitle!!, color = McColors.TextDim, fontSize = 14.sp)
}
Spacer(Modifier.height(4.dp))
McButton(
"Markeren als gezien",
primary = true,
focusRequester = menuFocus,
) {
scope.launch {
runCatching {
if (menuItem.type == "movie") {
api.markWatched(movieId = menuItem.id)
} else {
api.markWatched(episodeId = menuItem.id)
}
reloadHome()
}.onFailure { error = it.message }
continueMenu = null
}
}
McButton("Verbergen") {
scope.launch {
val pid = menuItem.progressId
if (!pid.isNullOrBlank()) {
runCatching {
api.dismissContinue(pid)
reloadHome()
}.onFailure { error = it.message }
}
continueMenu = null
}
}
McButton("Annuleren") { continueMenu = null }
}
}
ShelfBlock(
title = shelf.title,
items = shelf.items,
onSeeAll = { onSeeAll(catalogId, shelf.title) },
onClick = { item ->
if (item.type == "movie") onOpenMovie(item.id) else onOpenSeries(item.id)
},
)
}
}
}
@ -677,6 +831,7 @@ private fun ShelfBlock(
items: List<MediaItem>,
onSeeAll: (() -> Unit)?,
onClick: (MediaItem) -> Unit,
onLongClick: ((MediaItem) -> Unit)? = null,
) {
Column(Modifier.padding(start = 48.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
Row(
@ -699,7 +854,11 @@ private fun ShelfBlock(
contentPadding = PaddingValues(end = 48.dp),
) {
items(items, key = { "${it.type}:${it.id}:${it.progressId}" }) { item ->
PosterCard(item, onClick = { onClick(item) })
PosterCard(
item = item,
onClick = { onClick(item) },
onLongClick = onLongClick?.let { handler -> { handler(item) } },
)
}
}
}
@ -720,6 +879,7 @@ private fun MovieDetailScreen(
var files by remember { mutableStateOf<List<Pair<String, String>>>(emptyList()) }
var error by remember { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope()
val playFocus = remember { FocusRequester() }
LaunchedEffect(movieId) {
try {
@ -751,6 +911,13 @@ private fun MovieDetailScreen(
}
}
LaunchedEffect(files) {
if (files.isNotEmpty()) {
delay(80)
runCatching { playFocus.requestFocus() }
}
}
BackHandler(onBack = onBack)
Box(Modifier.fillMaxSize().background(McColors.Bg)) {
AsyncImage(model = backdrop, contentDescription = null, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
@ -767,7 +934,7 @@ private fun MovieDetailScreen(
if (error != null) Text(error!!, color = McColors.Danger)
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
files.firstOrNull()?.let { (id, _) ->
McButton("Afspelen", primary = true) { onPlay(id, title, 0) }
McButton("Afspelen", primary = true, focusRequester = playFocus) { onPlay(id, title, 0) }
}
McButton("Als gekeken") {
scope.launch {
@ -801,6 +968,7 @@ private fun SeriesDetailScreen(
var backdrop by remember { mutableStateOf<String?>(null) }
var episodes by remember { mutableStateOf<List<Triple<String, String, String>>>(emptyList()) }
var error by remember { mutableStateOf<String?>(null) }
val firstEpFocus = remember { FocusRequester() }
LaunchedEffect(seriesId) {
try {
@ -829,6 +997,13 @@ private fun SeriesDetailScreen(
}
}
LaunchedEffect(episodes) {
if (episodes.isNotEmpty()) {
delay(80)
runCatching { firstEpFocus.requestFocus() }
}
}
BackHandler(onBack = onBack)
Box(Modifier.fillMaxSize().background(McColors.Bg)) {
AsyncImage(model = backdrop, contentDescription = null, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, alpha = 0.35f)
@ -843,8 +1018,12 @@ private fun SeriesDetailScreen(
Spacer(Modifier.height(16.dp))
Text("Afleveringen", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.SemiBold)
}
items(episodes) { (id, label, fileId) ->
McButton(label) { onPlay(fileId, id, "$title · $label") }
itemsIndexed(episodes) { index, (id, label, fileId) ->
McButton(
label,
primary = index == 0,
focusRequester = if (index == 0) firstEpFocus else null,
) { onPlay(fileId, id, "$title · $label") }
}
}
}

View file

@ -194,8 +194,11 @@ fun PlayerScreen(
when {
menu == "os" -> menu = "text"
menu != null -> menu = null
controls -> onBack()
else -> showControls()
controls -> {
menu = null
controls = false
}
else -> onBack()
}
}

View file

@ -1,5 +1,6 @@
package nl.vonas.mediacluster.tv.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -13,8 +14,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -42,6 +43,7 @@ fun SearchScreen(
api: ApiClient,
onOpenMovie: (String) -> Unit,
onOpenSeries: (String) -> Unit,
onAtTopBack: (() -> Unit)? = null,
) {
var query by remember { mutableStateOf("") }
var results by remember { mutableStateOf<List<MediaItem>>(emptyList()) }
@ -49,6 +51,19 @@ fun SearchScreen(
var error by remember { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope()
var searchJob by remember { mutableStateOf<Job?>(null) }
val gridState = rememberLazyGridState()
if (onAtTopBack != null) {
BackHandler {
val scrolled = gridState.firstVisibleItemIndex > 0 ||
gridState.firstVisibleItemScrollOffset > 0
if (scrolled) {
scope.launch { gridState.animateScrollToItem(0) }
} else {
onAtTopBack()
}
}
}
fun scheduleSearch(next: String) {
query = next
@ -127,6 +142,7 @@ fun SearchScreen(
Spacer(Modifier.height(12.dp))
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 140.dp),
state = gridState,
contentPadding = PaddingValues(bottom = 40.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),

View file

@ -12,6 +12,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
@ -35,6 +37,7 @@ fun McButton(
label: String,
modifier: Modifier = Modifier,
primary: Boolean = false,
focusRequester: FocusRequester? = null,
onClick: () -> Unit,
) {
val bg = if (primary) Color.White else Color(0x33FFFFFF)
@ -43,7 +46,9 @@ fun McButton(
val fgFocus = Color.Black
Surface(
onClick = onClick,
modifier = modifier,
modifier = modifier.then(
if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier,
),
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(4.dp)),
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.06f),
glow = ClickableSurfaceDefaults.glow(
@ -76,6 +81,7 @@ fun PosterCard(
modifier: Modifier = Modifier,
width: Dp = 140.dp,
height: Dp = 210.dp,
onLongClick: (() -> Unit)? = null,
) {
val progress = if (item.durationMs > 0) {
(item.positionMs.toFloat() / item.durationMs.toFloat()).coerceIn(0f, 1f)
@ -85,6 +91,7 @@ fun PosterCard(
Surface(
onClick = onClick,
onLongClick = onLongClick,
modifier = modifier.width(width),
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(4.dp)),
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.14f),
@ -117,17 +124,10 @@ fun PosterCard(
Box(
Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.height(3.dp)
.background(Color(0x55FFFFFF)),
) {
Box(
Modifier
.fillMaxWidth(progress)
.height(3.dp)
.background(McColors.Accent),
)
}
.fillMaxWidth(progress)
.height(4.dp)
.background(McColors.Brand),
)
}
}
}

View file

@ -757,7 +757,9 @@ export class ViewerService {
? { hiddenFromContinue: body.hiddenFromContinue }
: autoComplete
? { hiddenFromContinue: true }
: {}),
: positionMs > 30_000
? { hiddenFromContinue: false }
: {}),
},
});
}
@ -791,7 +793,9 @@ export class ViewerService {
? { hiddenFromContinue: body.hiddenFromContinue }
: autoComplete
? { hiddenFromContinue: true }
: {}),
: positionMs > 30_000
? { hiddenFromContinue: false }
: {}),
},
});
}