Merge remote-tracking branch 'origin/main' into develop/mpv

This commit is contained in:
Damontecres 2025-11-05 16:47:35 -05:00
commit 9a093afd38
No known key found for this signature in database
17 changed files with 326 additions and 341 deletions

View file

@ -655,6 +655,18 @@ sealed interface AppPreference<T> {
summaryOff = R.string.disabled, summaryOff = R.string.disabled,
) )
val OneClickPause =
AppSwitchPreference(
title = R.string.one_click_pause,
defaultValue = false,
getter = { it.playbackPreferences.oneClickPause },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { oneClickPause = value }
},
summaryOn = R.string.one_click_pause_summary_on,
summaryOff = R.string.disabled,
)
val PlayerBackendPref = val PlayerBackendPref =
AppChoicePreference<PlayerBackend>( AppChoicePreference<PlayerBackend>(
title = R.string.player_backend, title = R.string.player_backend,
@ -756,6 +768,7 @@ val advancedPreferences =
title = R.string.playback, title = R.string.playback,
preferences = preferences =
listOf( listOf(
AppPreference.OneClickPause,
AppPreference.SkipIntros, AppPreference.SkipIntros,
AppPreference.SkipOutros, AppPreference.SkipOutros,
AppPreference.SkipCommercials, AppPreference.SkipCommercials,

View file

@ -4,8 +4,11 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.focusGroup import androidx.compose.foundation.focusGroup
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.Row
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectable
@ -33,12 +36,17 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.tv.material3.Icon import androidx.tv.material3.Icon
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.playOnClickSound import com.github.damontecres.wholphin.ui.playOnClickSound
import com.github.damontecres.wholphin.ui.playSoundOnFocus import com.github.damontecres.wholphin.ui.playSoundOnFocus
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import java.util.Locale
enum class StarRatingPrecision { enum class StarRatingPrecision {
FULL, FULL,
@ -63,6 +71,33 @@ val EmptyStarColor = Color(0x2AFFC700)
val ratingBarHeight: Dp = 32.dp val ratingBarHeight: Dp = 32.dp
@Composable
fun SimpleStarRating(
communityRating: Float?,
modifier: Modifier = Modifier,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
communityRating?.let {
Text(
text = String.format(Locale.getDefault(), "%.1f", it),
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier,
)
Icon(
imageVector = Icons.Filled.Star,
tint = FilledStarColor,
contentDescription = null,
modifier = Modifier,
)
}
}
}
/** /**
* Shows a rating out of 5 stars * Shows a rating out of 5 stars
*/ */
@ -211,3 +246,15 @@ fun StarRating(
} }
} }
} }
@PreviewTvSpec
@Composable
private fun SimpleStarRatingPreview() {
WholphinTheme {
Column {
SimpleStarRating(7.5f, Modifier.height(32.dp))
SimpleStarRating(7.5f, Modifier.height(20.dp))
SimpleStarRating(null, Modifier.height(32.dp))
}
}
}

View file

@ -0,0 +1,182 @@
package com.github.damontecres.wholphin.ui.components
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
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
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.ui.tryRequestFocus
@Composable
fun TabRow(
selectedTabIndex: Int,
tabs: List<String>,
onClick: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
val state = rememberLazyListState()
LaunchedEffect(Unit) {
state.animateScrollToItem(selectedTabIndex)
}
val focusRequesters = remember(tabs) { List(tabs.size) { FocusRequester() } }
var rowHasFocus by remember { mutableStateOf(false) }
LazyRow(
state = state,
modifier =
modifier
.onFocusChanged {
rowHasFocus = it.hasFocus
}.focusProperties {
onEnter = {
focusRequesters.getOrNull(selectedTabIndex)?.tryRequestFocus()
}
},
) {
itemsIndexed(tabs) { index, tabTitle ->
val interactionSource = remember { MutableInteractionSource() }
Tab(
title = tabTitle,
selected = index == selectedTabIndex,
rowActive = rowHasFocus,
interactionSource = interactionSource,
onClick = {
onClick.invoke(index)
},
modifier = Modifier.focusRequester(focusRequesters[index]),
)
}
}
}
@Composable
fun Tab(
title: String,
selected: Boolean,
rowActive: Boolean,
onClick: () -> Unit,
interactionSource: MutableInteractionSource,
modifier: Modifier = Modifier,
) {
var tabWidth by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
val focused by interactionSource.collectIsFocusedAsState()
val contentColor =
if (rowActive || selected) {
MaterialTheme.colorScheme.onSurface
} else {
MaterialTheme.colorScheme.onSurface.copy(alpha = .5f)
}
Box(
modifier =
modifier
.clickable(
enabled = true,
interactionSource = interactionSource,
onClick = onClick,
indication = null,
).onGloballyPositioned {
tabWidth = with(density) { it.size.width.toDp() }
},
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier,
) {
Text(
text = title,
fontSize = 16.sp,
color = contentColor,
modifier = Modifier.padding(horizontal = 16.dp),
)
TabIndicator(
selected = selected,
rowActive = rowActive,
focused = focused,
tabWidth = tabWidth,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
}
}
@Composable
fun TabIndicator(
selected: Boolean,
rowActive: Boolean,
focused: Boolean,
tabWidth: Dp,
modifier: Modifier = Modifier,
) {
val width by animateDpAsState(if (rowActive && focused) tabWidth else tabWidth * .25f)
val backgroundColor =
if (rowActive && focused) {
MaterialTheme.colorScheme.border
} else if (selected) {
MaterialTheme.colorScheme.onSurface
} else {
Color.Transparent
}
Box(
modifier =
modifier
.height(2.dp)
.fillMaxWidth()
.width(width)
.background(backgroundColor),
)
}
@PreviewTvSpec
@Composable
private fun TabRowPreview() {
WholphinTheme {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
TabRow(
selectedTabIndex = 1,
tabs = listOf("Tab 1", "Tab 2", "Tab 3"),
onClick = {},
)
Tab(
title = "This is a Tab",
selected = true,
rowActive = true,
onClick = {},
interactionSource = remember { MutableInteractionSource() },
modifier = Modifier.width(120.dp),
)
}
}
}

View file

@ -20,20 +20,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
@ -41,10 +33,10 @@ import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.VideoSortOptions import com.github.damontecres.wholphin.ui.data.VideoSortOptions
import com.github.damontecres.wholphin.ui.detail.livetv.DvrSchedule import com.github.damontecres.wholphin.ui.detail.livetv.DvrSchedule
import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.nav.NavigationManager
@ -103,7 +95,6 @@ fun CollectionFolderLiveTv(
TabId(stringResource(R.string.tv_dvr_schedule), UUID.randomUUID()), TabId(stringResource(R.string.tv_dvr_schedule), UUID.randomUUID()),
) + folders ) + folders
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@ -129,55 +120,13 @@ fun CollectionFolderLiveTv(
exit = slideOutVertically() + fadeOut(), exit = slideOutVertically() + fadeOut(),
) { ) {
TabRow( TabRow(
selectedTabIndex = focusTabIndex, selectedTabIndex = selectedTabIndex,
modifier = modifier =
Modifier Modifier
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp) .padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
.focusRestorer(firstTabFocusRequester) .focusRequester(firstTabFocusRequester),
.onFocusChanged { tabs = tabs.map { it.title },
if (!it.isFocused) { onClick = { selectedTabIndex = it },
focusTabIndex = selectedTabIndex
}
},
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
tabs = {
tabs.forEachIndexed { index, tabId ->
Tab(
selected = focusTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = { focusTabIndex = index },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = tabId.title,
style = MaterialTheme.typography.titleMedium,
modifier =
Modifier
.padding(8.dp)
.ifElse(
index == selectedTabIndex,
Modifier.focusRequester(firstTabFocusRequester),
),
)
}
}
},
) )
} }
when (selectedTabIndex) { when (selectedTabIndex) {

View file

@ -19,17 +19,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
@ -38,9 +30,9 @@ import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreCardGrid import com.github.damontecres.wholphin.ui.components.GenreCardGrid
import com.github.damontecres.wholphin.ui.components.RecommendedMovie import com.github.damontecres.wholphin.ui.components.RecommendedMovie
import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.MovieSortOptions import com.github.damontecres.wholphin.ui.data.MovieSortOptions
import com.github.damontecres.wholphin.ui.data.VideoSortOptions import com.github.damontecres.wholphin.ui.data.VideoSortOptions
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -63,7 +55,6 @@ fun CollectionFolderMovie(
stringResource(R.string.collections), stringResource(R.string.collections),
stringResource(R.string.genres), stringResource(R.string.genres),
) )
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@ -90,55 +81,13 @@ fun CollectionFolderMovie(
exit = slideOutVertically() + fadeOut(), exit = slideOutVertically() + fadeOut(),
) { ) {
TabRow( TabRow(
selectedTabIndex = focusTabIndex, selectedTabIndex = selectedTabIndex,
modifier = modifier =
Modifier Modifier
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp) .padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
.focusRestorer(firstTabFocusRequester) .focusRequester(firstTabFocusRequester),
.onFocusChanged { tabs = tabs,
if (!it.isFocused) { onClick = { selectedTabIndex = it },
focusTabIndex = selectedTabIndex
}
},
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = focusTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = { focusTabIndex = index },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier =
Modifier
.padding(8.dp)
.ifElse(
index == selectedTabIndex,
Modifier.focusRequester(firstTabFocusRequester),
),
)
}
}
},
) )
} }
when (selectedTabIndex) { when (selectedTabIndex) {

View file

@ -19,17 +19,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
@ -38,8 +30,8 @@ import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreCardGrid import com.github.damontecres.wholphin.ui.components.GenreCardGrid
import com.github.damontecres.wholphin.ui.components.RecommendedTvShow import com.github.damontecres.wholphin.ui.components.RecommendedTvShow
import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -61,7 +53,6 @@ fun CollectionFolderTv(
stringResource(R.string.library), stringResource(R.string.library),
stringResource(R.string.genres), stringResource(R.string.genres),
) )
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@ -88,55 +79,13 @@ fun CollectionFolderTv(
exit = slideOutVertically() + fadeOut(), exit = slideOutVertically() + fadeOut(),
) { ) {
TabRow( TabRow(
selectedTabIndex = focusTabIndex, selectedTabIndex = selectedTabIndex,
modifier = modifier =
Modifier Modifier
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp) .padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
.focusRestorer(firstTabFocusRequester) .focusRequester(firstTabFocusRequester),
.onFocusChanged { tabs = tabs,
if (!it.isFocused) { onClick = { selectedTabIndex = it },
focusTabIndex = selectedTabIndex
}
},
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = focusTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = { focusTabIndex = index },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier =
Modifier
.padding(8.dp)
.ifElse(
index == selectedTabIndex,
Modifier.focusRequester(firstTabFocusRequester),
),
)
}
}
},
) )
} }
when (selectedTabIndex) { when (selectedTabIndex) {

View file

@ -19,23 +19,16 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions
import com.github.damontecres.wholphin.ui.data.MovieSortOptions import com.github.damontecres.wholphin.ui.data.MovieSortOptions
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
@ -66,14 +59,9 @@ fun FavoritesPage(
stringResource(R.string.tv_shows), stringResource(R.string.tv_shows),
stringResource(R.string.episodes), stringResource(R.string.episodes),
) )
val tabFocusRequesters = remember { List(tabs.size) { FocusRequester() } }
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
tabFocusRequesters.getOrNull(selectedTabIndex)?.tryRequestFocus()
}
LaunchedEffect(selectedTabIndex) { LaunchedEffect(selectedTabIndex) {
preferencesViewModel.saveRememberedTab( preferencesViewModel.saveRememberedTab(
preferences, preferences,
@ -97,52 +85,12 @@ fun FavoritesPage(
exit = slideOutVertically() + fadeOut(), exit = slideOutVertically() + fadeOut(),
) { ) {
TabRow( TabRow(
selectedTabIndex = focusTabIndex, selectedTabIndex = selectedTabIndex,
modifier = modifier =
Modifier Modifier
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp) .padding(start = 32.dp, top = 16.dp, bottom = 16.dp),
.focusRestorer(tabFocusRequesters[0]) tabs = tabs,
.onFocusChanged { onClick = { selectedTabIndex = it },
if (!it.isFocused) {
focusTabIndex = selectedTabIndex
}
},
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = focusTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = { focusTabIndex = index },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier =
Modifier
.padding(8.dp)
.focusRequester(tabFocusRequesters[index]),
)
}
}
},
) )
} }
when (selectedTabIndex) { when (selectedTabIndex) {

View file

@ -2,6 +2,8 @@ package com.github.damontecres.wholphin.ui.detail
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -9,6 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -17,6 +21,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
@ -322,9 +327,11 @@ fun PersonHeader(
overviewOnClick: () -> Unit, overviewOnClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Row( Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(32.dp), horizontalArrangement = Arrangement.spacedBy(32.dp),
modifier = modifier, modifier = modifier.bringIntoViewRequester(bringIntoViewRequester),
) { ) {
AsyncImage( AsyncImage(
model = imageUrl, model = imageUrl,
@ -414,10 +421,18 @@ fun PersonHeader(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} }
val interactionSource = remember { MutableInteractionSource() }
val focused by interactionSource.collectIsFocusedAsState()
if (focused) {
LaunchedEffect(Unit) {
bringIntoViewRequester.bringIntoView()
}
}
OverviewText( OverviewText(
overview = overview ?: "", overview = overview ?: "",
maxLines = 3, maxLines = 3,
onClick = overviewOnClick, onClick = overviewOnClick,
interactionSource = interactionSource,
modifier = Modifier.padding(top = 8.dp), modifier = Modifier.padding(top = 8.dp),
) )
} }

View file

@ -5,7 +5,6 @@ import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -34,8 +33,7 @@ import com.github.damontecres.wholphin.data.model.chooseStream
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.StarRating import com.github.damontecres.wholphin.ui.components.SimpleStarRating
import com.github.damontecres.wholphin.ui.components.StarRatingPrecision
import com.github.damontecres.wholphin.ui.components.TitleValueText import com.github.damontecres.wholphin.ui.components.TitleValueText
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
@ -102,24 +100,13 @@ fun MovieDetailsHeader(
DotSeparatedRow( DotSeparatedRow(
texts = details, texts = details,
textStyle = MaterialTheme.typography.titleMedium, textStyle = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(start = 8.dp), modifier = Modifier,
) )
} }
dto.communityRating?.let { SimpleStarRating(
if (it > 0f) { dto.communityRating,
StarRating( Modifier.height(20.dp),
rating100 = (it * 10).toInt(),
onRatingChange = {},
enabled = false,
precision = StarRatingPrecision.HALF,
playSoundOnFocus = true,
modifier = Modifier.height(32.dp),
) )
} else {
Spacer(Modifier.height(32.dp))
}
}
dto.taglines?.firstOrNull()?.let { tagline -> dto.taglines?.firstOrNull()?.let { tagline ->
Text( Text(
text = tagline, text = tagline,

View file

@ -3,7 +3,6 @@ package com.github.damontecres.wholphin.ui.detail.series
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -16,8 +15,7 @@ import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.StarRating import com.github.damontecres.wholphin.ui.components.SimpleStarRating
import com.github.damontecres.wholphin.ui.components.StarRatingPrecision
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.timeRemaining import com.github.damontecres.wholphin.ui.timeRemaining
import com.github.damontecres.wholphin.util.formatDateTime import com.github.damontecres.wholphin.util.formatDateTime
@ -69,20 +67,10 @@ fun FocusedEpisodeHeader(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
dto.communityRating?.let { SimpleStarRating(
if (it > 0f) { dto.communityRating,
StarRating( Modifier.height(20.dp),
rating100 = (it * 10).toInt(),
onRatingChange = {},
enabled = false,
precision = StarRatingPrecision.HALF,
playSoundOnFocus = true,
modifier = Modifier.height(24.dp),
) )
} else {
Spacer(Modifier.height(24.dp))
}
} ?: Spacer(Modifier.height(24.dp))
} }
OverviewText( OverviewText(
overview = dto.overview ?: "", overview = dto.overview ?: "",

View file

@ -61,8 +61,7 @@ import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
import com.github.damontecres.wholphin.ui.components.LoadingPage import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.StarRating import com.github.damontecres.wholphin.ui.components.SimpleStarRating
import com.github.damontecres.wholphin.ui.components.StarRatingPrecision
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
@ -467,18 +466,10 @@ fun SeriesDetailsHeader(
) )
} }
dto.communityRating?.let { SimpleStarRating(
if (it >= 0f) { dto.communityRating,
StarRating( Modifier.height(20.dp),
rating100 = (it * 10).toInt(),
onRatingChange = {},
enabled = false,
precision = StarRatingPrecision.HALF,
playSoundOnFocus = true,
modifier = Modifier.height(24.dp),
) )
}
}
dto.overview?.let { overview -> dto.overview?.let { overview ->
OverviewText( OverviewText(

View file

@ -29,7 +29,6 @@ import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
@ -37,10 +36,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text import androidx.tv.material3.Text
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
@ -52,6 +47,7 @@ import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.cards.BannerCard import com.github.damontecres.wholphin.ui.cards.BannerCard
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.LoadingPage import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.ifElse import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -80,10 +76,7 @@ fun SeriesOverviewContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val bringIntoViewRequester = remember { BringIntoViewRequester() } val bringIntoViewRequester = remember { BringIntoViewRequester() }
var focusTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) }
var selectedTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) } var selectedTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) }
val focusRequesters = remember(seasons.size) { List(seasons.size) { FocusRequester() } }
var resolvedTabIndex by remember { mutableIntStateOf(selectedTabIndex) }
val tabRowFocusRequester = remember { FocusRequester() } val tabRowFocusRequester = remember { FocusRequester() }
val focusedEpisode = val focusedEpisode =
@ -139,63 +132,23 @@ fun SeriesOverviewContent(
PaddingValues(start = 16.dp, end = 16.dp) PaddingValues(start = 16.dp, end = 16.dp)
} }
TabRow( TabRow(
selectedTabIndex = focusTabIndex, selectedTabIndex = selectedTabIndex,
modifier = tabs =
Modifier seasons.mapNotNull {
.ifElse( it?.name
focusRequesters.size > selectedTabIndex, ?: (stringResource(R.string.tv_season) + " ${it?.data?.indexNumber}")
{ Modifier.focusRestorer(focusRequesters[selectedTabIndex]) },
).focusRequester(tabRowFocusRequester)
.padding(paddingValues)
.fillMaxWidth()
.onFocusChanged {
if (!it.isFocused) {
focusTabIndex = selectedTabIndex
}
}, },
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
) {
seasons.forEachIndexed { index, season ->
season?.let { season ->
Tab(
selected = focusTabIndex == index,
onFocus = { focusTabIndex = index },
onClick = { onClick = {
selectedTabIndex = index selectedTabIndex = it
onFocus.invoke(SeriesOverviewPosition(index, 0)) onFocus.invoke(SeriesOverviewPosition(it, 0))
}, },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
modifier = modifier =
Modifier Modifier
.focusRequester(focusRequesters[index]), .focusRequester(tabRowFocusRequester)
) { .padding(paddingValues)
Text( .fillMaxWidth(),
text =
season.name
?: (stringResource(R.string.tv_season) + " ${season.data.indexNumber}"),
modifier = Modifier.padding(8.dp),
) )
} }
}
}
}
}
item { item {
series.name?.let { series.name?.let {
Text( Text(

View file

@ -90,7 +90,7 @@ class HomeViewModel
if (resume.isNotEmpty()) { if (resume.isNotEmpty()) {
add( add(
HomeRow( HomeRow(
titleRes = R.string.recently_added, titleRes = R.string.continue_watching,
items = resume, items = resume,
), ),
) )

View file

@ -23,6 +23,7 @@ class PlaybackKeyHandler(
private val controllerViewState: ControllerViewState, private val controllerViewState: ControllerViewState,
private val updateSkipIndicator: (Long) -> Unit, private val updateSkipIndicator: (Long) -> Unit,
private val skipBackOnResume: Duration?, private val skipBackOnResume: Duration?,
private val oneClickPause: Boolean,
private val onInteraction: () -> Unit, private val onInteraction: () -> Unit,
) { ) {
fun onKeyEvent(it: KeyEvent): Boolean { fun onKeyEvent(it: KeyEvent): Boolean {
@ -33,7 +34,7 @@ class PlaybackKeyHandler(
result = false result = false
} else if (it.type != KeyEventType.KeyUp) { } else if (it.type != KeyEventType.KeyUp) {
result = false result = false
} else if (isDpad(it)) { } else if (isDirectionalDpad(it) || isEnterKey(it)) {
if (!controllerViewState.controlsVisible) { if (!controllerViewState.controlsVisible) {
if (skipWithLeftRight && it.key == Key.DirectionLeft) { if (skipWithLeftRight && it.key == Key.DirectionLeft) {
updateSkipIndicator(-seekBack.inWholeMilliseconds) updateSkipIndicator(-seekBack.inWholeMilliseconds)
@ -41,6 +42,15 @@ class PlaybackKeyHandler(
} else if (skipWithLeftRight && it.key == Key.DirectionRight) { } else if (skipWithLeftRight && it.key == Key.DirectionRight) {
player.seekForward(seekForward) player.seekForward(seekForward)
updateSkipIndicator(seekForward.inWholeMilliseconds) updateSkipIndicator(seekForward.inWholeMilliseconds)
} else if (oneClickPause && isEnterKey(it)) {
Util.handlePlayPauseButtonAction(player)
if (!player.isPlaying) {
controllerViewState.showControls()
} else {
skipBackOnResume?.let {
player.seekBack(it)
}
}
} else { } else {
controllerViewState.showControls() controllerViewState.showControls()
} }

View file

@ -208,6 +208,7 @@ fun PlaybackPage(
updateSkipIndicator = updateSkipIndicator, updateSkipIndicator = updateSkipIndicator,
skipBackOnResume = preferences.appPreferences.playbackPreferences.skipBackOnResume, skipBackOnResume = preferences.appPreferences.playbackPreferences.skipBackOnResume,
onInteraction = viewModel::reportInteraction, onInteraction = viewModel::reportInteraction,
oneClickPause = preferences.appPreferences.playbackPreferences.oneClickPause,
) )
val showSegment = val showSegment =

View file

@ -67,8 +67,9 @@ message PlaybackPreferences {
int64 pass_out_protection_ms = 16; int64 pass_out_protection_ms = 16;
PrefContentScale global_content_scale = 17; PrefContentScale global_content_scale = 17;
ShowNextUpWhen show_next_up_when = 18; ShowNextUpWhen show_next_up_when = 18;
PlayerBackend player_backend = 19; bool one_click_pause = 19;
MpvOptions mpv_options = 20; PlayerBackend player_backend = 20;
MpvOptions mpv_options = 21;
} }
message HomePagePreferences{ message HomePagePreferences{

View file

@ -142,6 +142,8 @@
<string name="aired_episode_order">Aired Episode Order</string> <string name="aired_episode_order">Aired Episode Order</string>
<string name="create_playlist">Create new playlist</string> <string name="create_playlist">Create new playlist</string>
<string name="add_to_playlist">Add to playlist</string> <string name="add_to_playlist">Add to playlist</string>
<string name="one_click_pause">Pause with one click</string>
<string name="one_click_pause_summary_on">Press D-Pad center to pause/play</string>
<string name="player_backend">Playback Backend</string> <string name="player_backend">Playback Backend</string>
<string name="mpv_hardware_decoding">MPV: Use hardware decoding</string> <string name="mpv_hardware_decoding">MPV: Use hardware decoding</string>
<string name="disable_if_crash">Disable if you experience crashes</string> <string name="disable_if_crash">Disable if you experience crashes</string>