mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Use app preferences
This commit is contained in:
parent
3dd78f175b
commit
9e876a85d4
18 changed files with 146 additions and 45 deletions
|
|
@ -3,6 +3,7 @@ package com.github.damontecres.dolphin
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
|
@ -57,7 +58,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
DolphinTheme(true) {
|
DolphinTheme(true) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.background),
|
||||||
shape = RectangleShape,
|
shape = RectangleShape,
|
||||||
) {
|
) {
|
||||||
CoilConfig(serverRepository, okHttpClient, false)
|
CoilConfig(serverRepository, okHttpClient, false)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package com.github.damontecres.dolphin.ui.preferences
|
package com.github.damontecres.dolphin.preferences
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.ArrayRes
|
import androidx.annotation.ArrayRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
import com.github.damontecres.dolphin.preferences.AppPreferences
|
|
||||||
import com.github.damontecres.dolphin.preferences.updatePlaybackPreferences
|
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
|
import com.github.damontecres.dolphin.ui.preferences.PreferenceGroup
|
||||||
|
import com.github.damontecres.dolphin.ui.preferences.PreferenceValidation
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
@ -149,6 +149,32 @@ sealed interface AppPreference<T> {
|
||||||
summarizer = { value -> value?.toString() },
|
summarizer = { value -> value?.toString() },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val HomePageItems =
|
||||||
|
AppSliderPreference(
|
||||||
|
title = R.string.max_homepage_items,
|
||||||
|
defaultValue = 25,
|
||||||
|
min = 5,
|
||||||
|
max = 50,
|
||||||
|
interval = 1,
|
||||||
|
getter = { it.homePagePreferences.maxItemsPerRow.toLong() },
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.updateHomePagePreferences { maxItemsPerRow = value.toInt() }
|
||||||
|
},
|
||||||
|
summarizer = { value -> value?.toString() },
|
||||||
|
)
|
||||||
|
|
||||||
|
val RewatchNextUp =
|
||||||
|
AppSwitchPreference(
|
||||||
|
title = R.string.rewatch_next_up,
|
||||||
|
defaultValue = false,
|
||||||
|
getter = { it.homePagePreferences.enableRewatchingNextUp },
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.updateHomePagePreferences { enableRewatchingNextUp = value }
|
||||||
|
},
|
||||||
|
summaryOn = R.string.enabled,
|
||||||
|
summaryOff = R.string.disabled,
|
||||||
|
)
|
||||||
|
|
||||||
// val PlaybackDebugInfo =
|
// val PlaybackDebugInfo =
|
||||||
// AppSwitchPreference(
|
// AppSwitchPreference(
|
||||||
// title = R.string.playback_debug_info,
|
// title = R.string.playback_debug_info,
|
||||||
|
|
@ -194,6 +220,26 @@ sealed interface AppPreference<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val basicPreferences =
|
||||||
|
listOf(
|
||||||
|
PreferenceGroup(
|
||||||
|
title = R.string.basic_interface,
|
||||||
|
preferences =
|
||||||
|
listOf(
|
||||||
|
AppPreference.SkipForward,
|
||||||
|
AppPreference.SkipBack,
|
||||||
|
AppPreference.ControllerTimeout,
|
||||||
|
AppPreference.SeekBarSteps,
|
||||||
|
AppPreference.HomePageItems,
|
||||||
|
AppPreference.RewatchNextUp,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val uiPreferences = listOf<PreferenceGroup>()
|
||||||
|
|
||||||
|
val advancedPreferences = listOf<PreferenceGroup>()
|
||||||
|
|
||||||
data class AppSwitchPreference(
|
data class AppSwitchPreference(
|
||||||
@get:StringRes override val title: Int,
|
@get:StringRes override val title: Int,
|
||||||
override val defaultValue: Boolean,
|
override val defaultValue: Boolean,
|
||||||
|
|
@ -2,7 +2,6 @@ package com.github.damontecres.dolphin.preferences
|
||||||
|
|
||||||
import androidx.datastore.core.CorruptionException
|
import androidx.datastore.core.CorruptionException
|
||||||
import androidx.datastore.core.Serializer
|
import androidx.datastore.core.Serializer
|
||||||
import com.github.damontecres.dolphin.ui.preferences.AppPreference
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException
|
import com.google.protobuf.InvalidProtocolBufferException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
@ -27,6 +26,13 @@ class AppPreferencesSerializer
|
||||||
controllerTimeoutMs = AppPreference.ControllerTimeout.defaultValue
|
controllerTimeoutMs = AppPreference.ControllerTimeout.defaultValue
|
||||||
seekBarSteps = AppPreference.SeekBarSteps.defaultValue.toInt()
|
seekBarSteps = AppPreference.SeekBarSteps.defaultValue.toInt()
|
||||||
}.build()
|
}.build()
|
||||||
|
homePagePreferences =
|
||||||
|
HomePagePreferences
|
||||||
|
.newBuilder()
|
||||||
|
.apply {
|
||||||
|
maxItemsPerRow = AppPreference.HomePageItems.defaultValue.toInt()
|
||||||
|
enableRewatchingNextUp = AppPreference.RewatchNextUp.defaultValue
|
||||||
|
}.build()
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
override suspend fun readFrom(input: InputStream): AppPreferences {
|
override suspend fun readFrom(input: InputStream): AppPreferences {
|
||||||
|
|
@ -49,3 +55,8 @@ inline fun AppPreferences.updatePlaybackPreferences(block: PlaybackPreferences.B
|
||||||
update {
|
update {
|
||||||
playbackPreferences = playbackPreferences.toBuilder().apply(block).build()
|
playbackPreferences = playbackPreferences.toBuilder().apply(block).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun AppPreferences.updateHomePagePreferences(block: HomePagePreferences.Builder.() -> Unit): AppPreferences =
|
||||||
|
update {
|
||||||
|
homePagePreferences = homePagePreferences.toBuilder().apply(block).build()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
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.media3.common.Player
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
|
|
@ -181,3 +182,7 @@ val BaseItemDto.timeRemaining: Duration?
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Player.seekBack(amount: Duration) = seekTo((currentPosition - amount.inWholeMilliseconds).coerceAtLeast(0L))
|
||||||
|
|
||||||
|
fun Player.seekForward(amount: Duration) = seekTo((currentPosition + amount.inWholeMilliseconds).coerceAtMost(duration))
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.dolphin.ui.components.CircularProgress
|
import com.github.damontecres.dolphin.ui.components.CircularProgress
|
||||||
|
|
@ -67,6 +68,9 @@ fun MainPage(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
viewModel: MainViewModel = hiltViewModel(),
|
viewModel: MainViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
OneTimeLaunchedEffect {
|
||||||
|
viewModel.init(preferences)
|
||||||
|
}
|
||||||
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
|
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
|
||||||
when (val state = loading) {
|
when (val state = loading) {
|
||||||
is LoadingState.Error -> Text(text = "Error: ${state.message}", modifier = modifier)
|
is LoadingState.Error -> Text(text = "Error: ${state.message}", modifier = modifier)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
|
|
@ -34,7 +35,8 @@ class MainViewModel
|
||||||
val loadingState = MutableLiveData<LoadingState>(LoadingState.Loading)
|
val loadingState = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||||
val homeRows = MutableLiveData<List<HomeRow>>()
|
val homeRows = MutableLiveData<List<HomeRow>>()
|
||||||
|
|
||||||
init {
|
fun init(preferences: UserPreferences) {
|
||||||
|
val limit = preferences.appPreferences.homePagePreferences.maxItemsPerRow
|
||||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
val user = api.userApi.getCurrentUser().content
|
val user = api.userApi.getCurrentUser().content
|
||||||
val displayPrefs =
|
val displayPrefs =
|
||||||
|
|
@ -74,7 +76,7 @@ class MainViewModel
|
||||||
imageTypeLimit = 1,
|
imageTypeLimit = 1,
|
||||||
parentId = viewId,
|
parentId = viewId,
|
||||||
groupItems = true,
|
groupItems = true,
|
||||||
limit = 25,
|
limit = limit,
|
||||||
)
|
)
|
||||||
val latest =
|
val latest =
|
||||||
api.userLibraryApi
|
api.userLibraryApi
|
||||||
|
|
@ -94,6 +96,7 @@ class MainViewModel
|
||||||
GetResumeItemsRequest(
|
GetResumeItemsRequest(
|
||||||
userId = user.id,
|
userId = user.id,
|
||||||
fields = DefaultItemFields,
|
fields = DefaultItemFields,
|
||||||
|
limit = limit,
|
||||||
// TODO, more params?
|
// TODO, more params?
|
||||||
)
|
)
|
||||||
val items =
|
val items =
|
||||||
|
|
@ -116,8 +119,10 @@ class MainViewModel
|
||||||
fields = DefaultItemFields,
|
fields = DefaultItemFields,
|
||||||
imageTypeLimit = 1,
|
imageTypeLimit = 1,
|
||||||
parentId = null,
|
parentId = null,
|
||||||
limit = 25,
|
limit = limit,
|
||||||
enableResumable = false,
|
enableResumable = false,
|
||||||
|
enableUserData = true,
|
||||||
|
enableRewatching = preferences.appPreferences.homePagePreferences.enableRewatchingNextUp,
|
||||||
)
|
)
|
||||||
val nextUp =
|
val nextUp =
|
||||||
api.tvShowsApi
|
api.tvShowsApi
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,7 @@ fun NavDrawer(
|
||||||
NavigationDrawer(
|
NavigationDrawer(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.focusRequester(drawerFocusRequester)
|
.focusRequester(drawerFocusRequester),
|
||||||
.background(MaterialTheme.colorScheme.background),
|
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,14 @@ import kotlinx.coroutines.flow.debounce
|
||||||
|
|
||||||
class ControllerViewState internal constructor(
|
class ControllerViewState internal constructor(
|
||||||
@param:IntRange(from = 0)
|
@param:IntRange(from = 0)
|
||||||
private val hideMilliseconds: Int,
|
private val hideMilliseconds: Long,
|
||||||
val controlsEnabled: Boolean,
|
val controlsEnabled: Boolean,
|
||||||
) {
|
) {
|
||||||
private val channel = Channel<Int>(CONFLATED)
|
private val channel = Channel<Long>(CONFLATED)
|
||||||
private var _controlsVisible by mutableStateOf(false)
|
private var _controlsVisible by mutableStateOf(false)
|
||||||
val controlsVisible get() = _controlsVisible
|
val controlsVisible get() = _controlsVisible
|
||||||
|
|
||||||
fun showControls(milliseconds: Int = hideMilliseconds) {
|
fun showControls(milliseconds: Long = hideMilliseconds) {
|
||||||
if (controlsEnabled) {
|
if (controlsEnabled) {
|
||||||
_controlsVisible = true
|
_controlsVisible = true
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ class ControllerViewState internal constructor(
|
||||||
_controlsVisible = false
|
_controlsVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pulseControls(milliseconds: Int = hideMilliseconds) {
|
fun pulseControls(milliseconds: Long = hideMilliseconds) {
|
||||||
channel.trySend(milliseconds)
|
channel.trySend(milliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ class ControllerViewState internal constructor(
|
||||||
suspend fun observe() {
|
suspend fun observe() {
|
||||||
channel
|
channel
|
||||||
.consumeAsFlow()
|
.consumeAsFlow()
|
||||||
.debounce { it.toLong() }
|
.debounce { it }
|
||||||
.collect {
|
.collect {
|
||||||
_controlsVisible = false
|
_controlsVisible = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ fun PlaybackContent(
|
||||||
val controllerViewState =
|
val controllerViewState =
|
||||||
remember {
|
remember {
|
||||||
ControllerViewState(
|
ControllerViewState(
|
||||||
5_000,
|
preferences.appPreferences.playbackPreferences.controllerTimeoutMs,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}.also {
|
}.also {
|
||||||
|
|
@ -124,6 +124,8 @@ fun PlaybackContent(
|
||||||
player = player,
|
player = player,
|
||||||
controlsEnabled = true,
|
controlsEnabled = true,
|
||||||
skipWithLeftRight = true,
|
skipWithLeftRight = true,
|
||||||
|
seekForward = preferences.appPreferences.playbackPreferences.skipForwardMs.milliseconds,
|
||||||
|
seekBack = preferences.appPreferences.playbackPreferences.skipBackMs.milliseconds,
|
||||||
controllerViewState = controllerViewState,
|
controllerViewState = controllerViewState,
|
||||||
updateSkipIndicator = updateSkipIndicator,
|
updateSkipIndicator = updateSkipIndicator,
|
||||||
)
|
)
|
||||||
|
|
@ -199,6 +201,8 @@ fun PlaybackContent(
|
||||||
previousEnabled = previousState.isEnabled,
|
previousEnabled = previousState.isEnabled,
|
||||||
nextEnabled = nextState.isEnabled,
|
nextEnabled = nextState.isEnabled,
|
||||||
seekEnabled = true,
|
seekEnabled = true,
|
||||||
|
seekForward = preferences.appPreferences.playbackPreferences.skipForwardMs.milliseconds,
|
||||||
|
seekBack = preferences.appPreferences.playbackPreferences.skipBackMs.milliseconds,
|
||||||
onPlaybackActionClick = {
|
onPlaybackActionClick = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is PlaybackAction.PlaybackSpeed -> {
|
is PlaybackAction.PlaybackSpeed -> {
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,15 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
import com.github.damontecres.dolphin.ui.AppColors
|
import com.github.damontecres.dolphin.ui.AppColors
|
||||||
|
import com.github.damontecres.dolphin.ui.seekBack
|
||||||
|
import com.github.damontecres.dolphin.ui.seekForward
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
sealed interface PlaybackAction {
|
sealed interface PlaybackAction {
|
||||||
|
|
@ -110,6 +113,8 @@ fun PlaybackControls(
|
||||||
playbackSpeed: Float,
|
playbackSpeed: Float,
|
||||||
scale: ContentScale,
|
scale: ContentScale,
|
||||||
seekBarIntervals: Int,
|
seekBarIntervals: Int,
|
||||||
|
seekBack: Duration,
|
||||||
|
seekForward: Duration,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
initialFocusRequester: FocusRequester = remember { FocusRequester() },
|
initialFocusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
seekBarInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
seekBarInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
|
@ -127,7 +132,7 @@ fun PlaybackControls(
|
||||||
scope.launch(ExceptionHandler()) {
|
scope.launch(ExceptionHandler()) {
|
||||||
bringIntoViewRequester.bringIntoView()
|
bringIntoViewRequester.bringIntoView()
|
||||||
}
|
}
|
||||||
controllerViewState.pulseControls(Int.MAX_VALUE)
|
controllerViewState.pulseControls(Long.MAX_VALUE)
|
||||||
}
|
}
|
||||||
LaunchedEffect(controllerViewState.controlsVisible) {
|
LaunchedEffect(controllerViewState.controlsVisible) {
|
||||||
if (controllerViewState.controlsVisible) {
|
if (controllerViewState.controlsVisible) {
|
||||||
|
|
@ -170,6 +175,8 @@ fun PlaybackControls(
|
||||||
showPlay = showPlay,
|
showPlay = showPlay,
|
||||||
previousEnabled = previousEnabled,
|
previousEnabled = previousEnabled,
|
||||||
nextEnabled = nextEnabled,
|
nextEnabled = nextEnabled,
|
||||||
|
seekBack = seekBack,
|
||||||
|
seekForward = seekForward,
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
)
|
)
|
||||||
RightPlaybackButtons(
|
RightPlaybackButtons(
|
||||||
|
|
@ -430,6 +437,8 @@ fun PlaybackButtons(
|
||||||
showPlay: Boolean,
|
showPlay: Boolean,
|
||||||
previousEnabled: Boolean,
|
previousEnabled: Boolean,
|
||||||
nextEnabled: Boolean,
|
nextEnabled: Boolean,
|
||||||
|
seekBack: Duration,
|
||||||
|
seekForward: Duration,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -449,7 +458,7 @@ fun PlaybackButtons(
|
||||||
iconRes = R.drawable.baseline_fast_rewind_24,
|
iconRes = R.drawable.baseline_fast_rewind_24,
|
||||||
onClick = {
|
onClick = {
|
||||||
onControllerInteraction.invoke()
|
onControllerInteraction.invoke()
|
||||||
player.seekBack()
|
player.seekBack(seekBack)
|
||||||
},
|
},
|
||||||
onControllerInteraction = onControllerInteraction,
|
onControllerInteraction = onControllerInteraction,
|
||||||
)
|
)
|
||||||
|
|
@ -466,7 +475,7 @@ fun PlaybackButtons(
|
||||||
iconRes = R.drawable.baseline_fast_forward_24,
|
iconRes = R.drawable.baseline_fast_forward_24,
|
||||||
onClick = {
|
onClick = {
|
||||||
onControllerInteraction.invoke()
|
onControllerInteraction.invoke()
|
||||||
player.seekForward()
|
player.seekForward(seekForward)
|
||||||
},
|
},
|
||||||
onControllerInteraction = onControllerInteraction,
|
onControllerInteraction = onControllerInteraction,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,16 @@ import androidx.compose.ui.input.key.key
|
||||||
import androidx.compose.ui.input.key.type
|
import androidx.compose.ui.input.key.type
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.util.Util
|
import androidx.media3.common.util.Util
|
||||||
|
import com.github.damontecres.dolphin.ui.seekBack
|
||||||
|
import com.github.damontecres.dolphin.ui.seekForward
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
class PlaybackKeyHandler(
|
class PlaybackKeyHandler(
|
||||||
private val player: Player,
|
private val player: Player,
|
||||||
private val controlsEnabled: Boolean,
|
private val controlsEnabled: Boolean,
|
||||||
private val skipWithLeftRight: Boolean,
|
private val skipWithLeftRight: Boolean,
|
||||||
|
private val seekBack: Duration,
|
||||||
|
private val seekForward: Duration,
|
||||||
private val controllerViewState: ControllerViewState,
|
private val controllerViewState: ControllerViewState,
|
||||||
private val updateSkipIndicator: (Long) -> Unit,
|
private val updateSkipIndicator: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -24,11 +29,11 @@ class PlaybackKeyHandler(
|
||||||
} else if (isDpad(it)) {
|
} else if (isDpad(it)) {
|
||||||
if (!controllerViewState.controlsVisible) {
|
if (!controllerViewState.controlsVisible) {
|
||||||
if (skipWithLeftRight && it.key == Key.DirectionLeft) {
|
if (skipWithLeftRight && it.key == Key.DirectionLeft) {
|
||||||
updateSkipIndicator(-player.seekBackIncrement)
|
updateSkipIndicator(-seekBack.inWholeMilliseconds)
|
||||||
player.seekBack()
|
player.seekBack(seekBack)
|
||||||
} else if (skipWithLeftRight && it.key == Key.DirectionRight) {
|
} else if (skipWithLeftRight && it.key == Key.DirectionRight) {
|
||||||
player.seekForward()
|
player.seekForward(seekForward)
|
||||||
updateSkipIndicator(player.seekForwardIncrement)
|
updateSkipIndicator(seekForward.inWholeMilliseconds)
|
||||||
} else {
|
} else {
|
||||||
controllerViewState.showControls()
|
controllerViewState.showControls()
|
||||||
}
|
}
|
||||||
|
|
@ -54,13 +59,13 @@ class PlaybackKeyHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.MediaFastForward, Key.MediaSkipForward -> {
|
Key.MediaFastForward, Key.MediaSkipForward -> {
|
||||||
player.seekForward()
|
player.seekForward(seekForward)
|
||||||
updateSkipIndicator(player.seekForwardIncrement)
|
updateSkipIndicator(seekForward.inWholeMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.MediaRewind, Key.MediaSkipBackward -> {
|
Key.MediaRewind, Key.MediaSkipBackward -> {
|
||||||
player.seekBack()
|
player.seekBack(seekBack)
|
||||||
updateSkipIndicator(-player.seekBackIncrement)
|
updateSkipIndicator(-seekBack.inWholeMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
Key.MediaNext -> if (player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)) player.seekToNext()
|
Key.MediaNext -> if (player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)) player.seekToNext()
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import org.jellyfin.sdk.model.api.TrickplayInfo
|
import org.jellyfin.sdk.model.api.TrickplayInfo
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlaybackOverlay(
|
fun PlaybackOverlay(
|
||||||
|
|
@ -29,6 +30,8 @@ fun PlaybackOverlay(
|
||||||
previousEnabled: Boolean,
|
previousEnabled: Boolean,
|
||||||
nextEnabled: Boolean,
|
nextEnabled: Boolean,
|
||||||
seekEnabled: Boolean,
|
seekEnabled: Boolean,
|
||||||
|
seekBack: Duration,
|
||||||
|
seekForward: Duration,
|
||||||
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
||||||
onSeekBarChange: (Long) -> Unit,
|
onSeekBarChange: (Long) -> Unit,
|
||||||
showDebugInfo: Boolean,
|
showDebugInfo: Boolean,
|
||||||
|
|
@ -121,6 +124,8 @@ fun PlaybackOverlay(
|
||||||
playbackSpeed = playbackSpeed,
|
playbackSpeed = playbackSpeed,
|
||||||
scale = scale,
|
scale = scale,
|
||||||
seekBarIntervals = 16,
|
seekBarIntervals = 16,
|
||||||
|
seekBack = seekBack,
|
||||||
|
seekForward = seekForward,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,14 @@ import androidx.tv.material3.Switch
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppChoicePreference
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppClickablePreference
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppDestinationPreference
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppMultiChoicePreference
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppPreference
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppSliderPreference
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppStringPreference
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppSwitchPreference
|
||||||
import com.github.damontecres.dolphin.ui.components.DialogItem
|
import com.github.damontecres.dolphin.ui.components.DialogItem
|
||||||
import com.github.damontecres.dolphin.ui.components.DialogParams
|
import com.github.damontecres.dolphin.ui.components.DialogParams
|
||||||
import com.github.damontecres.dolphin.ui.components.DialogPopup
|
import com.github.damontecres.dolphin.ui.components.DialogPopup
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.github.damontecres.dolphin.ui.preferences
|
package com.github.damontecres.dolphin.ui.preferences
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppPreference
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,11 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppPreference
|
||||||
import com.github.damontecres.dolphin.preferences.AppPreferences
|
import com.github.damontecres.dolphin.preferences.AppPreferences
|
||||||
|
import com.github.damontecres.dolphin.preferences.advancedPreferences
|
||||||
|
import com.github.damontecres.dolphin.preferences.basicPreferences
|
||||||
|
import com.github.damontecres.dolphin.preferences.uiPreferences
|
||||||
import com.github.damontecres.dolphin.ui.ifElse
|
import com.github.damontecres.dolphin.ui.ifElse
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.dolphin.ui.playOnClickSound
|
import com.github.damontecres.dolphin.ui.playOnClickSound
|
||||||
|
|
@ -47,24 +51,6 @@ import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
val basicPreferences =
|
|
||||||
listOf(
|
|
||||||
PreferenceGroup(
|
|
||||||
title = R.string.basic_interface,
|
|
||||||
preferences =
|
|
||||||
listOf(
|
|
||||||
AppPreference.SkipForward,
|
|
||||||
AppPreference.SkipBack,
|
|
||||||
AppPreference.ControllerTimeout,
|
|
||||||
AppPreference.SeekBarSteps,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
val uiPreferences = listOf<PreferenceGroup>()
|
|
||||||
|
|
||||||
val advancedPreferences = listOf<PreferenceGroup>()
|
|
||||||
|
|
||||||
data class Release(
|
data class Release(
|
||||||
val version: String,
|
val version: String,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.ProvideTextStyle
|
import androidx.tv.material3.ProvideTextStyle
|
||||||
import androidx.tv.material3.contentColorFor
|
import androidx.tv.material3.contentColorFor
|
||||||
|
import com.github.damontecres.dolphin.preferences.AppSliderPreference
|
||||||
import com.github.damontecres.dolphin.ui.components.SliderBar
|
import com.github.damontecres.dolphin.ui.components.SliderBar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,16 @@ message PlaybackPreferences {
|
||||||
int32 seek_bar_steps = 4;
|
int32 seek_bar_steps = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message HomePagePreferences{
|
||||||
|
int32 max_items_per_row = 1;
|
||||||
|
bool enable_rewatching_next_up = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message AppPreferences {
|
message AppPreferences {
|
||||||
// The currently signed in server and user IDs, mostly for restoring a session
|
// The currently signed in server and user IDs, mostly for restoring a session
|
||||||
string current_server_id = 1;
|
string current_server_id = 1;
|
||||||
string current_user_id = 2;
|
string current_user_id = 2;
|
||||||
|
|
||||||
PlaybackPreferences playback_preferences = 3;
|
PlaybackPreferences playback_preferences = 3;
|
||||||
|
HomePagePreferences home_page_preferences = 4;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,6 @@
|
||||||
<string name="skip_back_preference">Skip back</string>
|
<string name="skip_back_preference">Skip back</string>
|
||||||
<string name="hide_controller_timeout">Hide playback controls</string>
|
<string name="hide_controller_timeout">Hide playback controls</string>
|
||||||
<string name="seek_bar_steps">Seek bar steps</string>
|
<string name="seek_bar_steps">Seek bar steps</string>
|
||||||
|
<string name="max_homepage_items">Max items on home page rows</string>
|
||||||
|
<string name="rewatch_next_up">Enable rewatching in next up</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue