mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Use app preferences
This commit is contained in:
parent
3dd78f175b
commit
9e876a85d4
18 changed files with 146 additions and 45 deletions
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.focus.onFocusChanged
|
|||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.media3.common.Player
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
|
|
@ -181,3 +182,7 @@ val BaseItemDto.timeRemaining: Duration?
|
|||
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 com.github.damontecres.dolphin.data.model.BaseItem
|
||||
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.ItemRow
|
||||
import com.github.damontecres.dolphin.ui.components.CircularProgress
|
||||
|
|
@ -67,6 +68,9 @@ fun MainPage(
|
|||
modifier: Modifier,
|
||||
viewModel: MainViewModel = hiltViewModel(),
|
||||
) {
|
||||
OneTimeLaunchedEffect {
|
||||
viewModel.init(preferences)
|
||||
}
|
||||
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
|
||||
when (val state = loading) {
|
||||
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.viewModelScope
|
||||
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.isNotNullOrBlank
|
||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||
|
|
@ -34,7 +35,8 @@ class MainViewModel
|
|||
val loadingState = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
val homeRows = MutableLiveData<List<HomeRow>>()
|
||||
|
||||
init {
|
||||
fun init(preferences: UserPreferences) {
|
||||
val limit = preferences.appPreferences.homePagePreferences.maxItemsPerRow
|
||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
val user = api.userApi.getCurrentUser().content
|
||||
val displayPrefs =
|
||||
|
|
@ -74,7 +76,7 @@ class MainViewModel
|
|||
imageTypeLimit = 1,
|
||||
parentId = viewId,
|
||||
groupItems = true,
|
||||
limit = 25,
|
||||
limit = limit,
|
||||
)
|
||||
val latest =
|
||||
api.userLibraryApi
|
||||
|
|
@ -94,6 +96,7 @@ class MainViewModel
|
|||
GetResumeItemsRequest(
|
||||
userId = user.id,
|
||||
fields = DefaultItemFields,
|
||||
limit = limit,
|
||||
// TODO, more params?
|
||||
)
|
||||
val items =
|
||||
|
|
@ -116,8 +119,10 @@ class MainViewModel
|
|||
fields = DefaultItemFields,
|
||||
imageTypeLimit = 1,
|
||||
parentId = null,
|
||||
limit = 25,
|
||||
limit = limit,
|
||||
enableResumable = false,
|
||||
enableUserData = true,
|
||||
enableRewatching = preferences.appPreferences.homePagePreferences.enableRewatchingNextUp,
|
||||
)
|
||||
val nextUp =
|
||||
api.tvShowsApi
|
||||
|
|
|
|||
|
|
@ -109,8 +109,7 @@ fun NavDrawer(
|
|||
NavigationDrawer(
|
||||
modifier =
|
||||
modifier
|
||||
.focusRequester(drawerFocusRequester)
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
.focusRequester(drawerFocusRequester),
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ import kotlinx.coroutines.flow.debounce
|
|||
|
||||
class ControllerViewState internal constructor(
|
||||
@param:IntRange(from = 0)
|
||||
private val hideMilliseconds: Int,
|
||||
private val hideMilliseconds: Long,
|
||||
val controlsEnabled: Boolean,
|
||||
) {
|
||||
private val channel = Channel<Int>(CONFLATED)
|
||||
private val channel = Channel<Long>(CONFLATED)
|
||||
private var _controlsVisible by mutableStateOf(false)
|
||||
val controlsVisible get() = _controlsVisible
|
||||
|
||||
fun showControls(milliseconds: Int = hideMilliseconds) {
|
||||
fun showControls(milliseconds: Long = hideMilliseconds) {
|
||||
if (controlsEnabled) {
|
||||
_controlsVisible = true
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ class ControllerViewState internal constructor(
|
|||
_controlsVisible = false
|
||||
}
|
||||
|
||||
fun pulseControls(milliseconds: Int = hideMilliseconds) {
|
||||
fun pulseControls(milliseconds: Long = hideMilliseconds) {
|
||||
channel.trySend(milliseconds)
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ class ControllerViewState internal constructor(
|
|||
suspend fun observe() {
|
||||
channel
|
||||
.consumeAsFlow()
|
||||
.debounce { it.toLong() }
|
||||
.debounce { it }
|
||||
.collect {
|
||||
_controlsVisible = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ fun PlaybackContent(
|
|||
val controllerViewState =
|
||||
remember {
|
||||
ControllerViewState(
|
||||
5_000,
|
||||
preferences.appPreferences.playbackPreferences.controllerTimeoutMs,
|
||||
true,
|
||||
)
|
||||
}.also {
|
||||
|
|
@ -124,6 +124,8 @@ fun PlaybackContent(
|
|||
player = player,
|
||||
controlsEnabled = true,
|
||||
skipWithLeftRight = true,
|
||||
seekForward = preferences.appPreferences.playbackPreferences.skipForwardMs.milliseconds,
|
||||
seekBack = preferences.appPreferences.playbackPreferences.skipBackMs.milliseconds,
|
||||
controllerViewState = controllerViewState,
|
||||
updateSkipIndicator = updateSkipIndicator,
|
||||
)
|
||||
|
|
@ -199,6 +201,8 @@ fun PlaybackContent(
|
|||
previousEnabled = previousState.isEnabled,
|
||||
nextEnabled = nextState.isEnabled,
|
||||
seekEnabled = true,
|
||||
seekForward = preferences.appPreferences.playbackPreferences.skipForwardMs.milliseconds,
|
||||
seekBack = preferences.appPreferences.playbackPreferences.skipBackMs.milliseconds,
|
||||
onPlaybackActionClick = {
|
||||
when (it) {
|
||||
is PlaybackAction.PlaybackSpeed -> {
|
||||
|
|
|
|||
|
|
@ -58,12 +58,15 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.dolphin.R
|
||||
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.util.ExceptionHandler
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
sealed interface PlaybackAction {
|
||||
|
|
@ -110,6 +113,8 @@ fun PlaybackControls(
|
|||
playbackSpeed: Float,
|
||||
scale: ContentScale,
|
||||
seekBarIntervals: Int,
|
||||
seekBack: Duration,
|
||||
seekForward: Duration,
|
||||
modifier: Modifier = Modifier,
|
||||
initialFocusRequester: FocusRequester = remember { FocusRequester() },
|
||||
seekBarInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
|
|
@ -127,7 +132,7 @@ fun PlaybackControls(
|
|||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
controllerViewState.pulseControls(Int.MAX_VALUE)
|
||||
controllerViewState.pulseControls(Long.MAX_VALUE)
|
||||
}
|
||||
LaunchedEffect(controllerViewState.controlsVisible) {
|
||||
if (controllerViewState.controlsVisible) {
|
||||
|
|
@ -170,6 +175,8 @@ fun PlaybackControls(
|
|||
showPlay = showPlay,
|
||||
previousEnabled = previousEnabled,
|
||||
nextEnabled = nextEnabled,
|
||||
seekBack = seekBack,
|
||||
seekForward = seekForward,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
RightPlaybackButtons(
|
||||
|
|
@ -430,6 +437,8 @@ fun PlaybackButtons(
|
|||
showPlay: Boolean,
|
||||
previousEnabled: Boolean,
|
||||
nextEnabled: Boolean,
|
||||
seekBack: Duration,
|
||||
seekForward: Duration,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -449,7 +458,7 @@ fun PlaybackButtons(
|
|||
iconRes = R.drawable.baseline_fast_rewind_24,
|
||||
onClick = {
|
||||
onControllerInteraction.invoke()
|
||||
player.seekBack()
|
||||
player.seekBack(seekBack)
|
||||
},
|
||||
onControllerInteraction = onControllerInteraction,
|
||||
)
|
||||
|
|
@ -466,7 +475,7 @@ fun PlaybackButtons(
|
|||
iconRes = R.drawable.baseline_fast_forward_24,
|
||||
onClick = {
|
||||
onControllerInteraction.invoke()
|
||||
player.seekForward()
|
||||
player.seekForward(seekForward)
|
||||
},
|
||||
onControllerInteraction = onControllerInteraction,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,11 +7,16 @@ import androidx.compose.ui.input.key.key
|
|||
import androidx.compose.ui.input.key.type
|
||||
import androidx.media3.common.Player
|
||||
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(
|
||||
private val player: Player,
|
||||
private val controlsEnabled: Boolean,
|
||||
private val skipWithLeftRight: Boolean,
|
||||
private val seekBack: Duration,
|
||||
private val seekForward: Duration,
|
||||
private val controllerViewState: ControllerViewState,
|
||||
private val updateSkipIndicator: (Long) -> Unit,
|
||||
) {
|
||||
|
|
@ -24,11 +29,11 @@ class PlaybackKeyHandler(
|
|||
} else if (isDpad(it)) {
|
||||
if (!controllerViewState.controlsVisible) {
|
||||
if (skipWithLeftRight && it.key == Key.DirectionLeft) {
|
||||
updateSkipIndicator(-player.seekBackIncrement)
|
||||
player.seekBack()
|
||||
updateSkipIndicator(-seekBack.inWholeMilliseconds)
|
||||
player.seekBack(seekBack)
|
||||
} else if (skipWithLeftRight && it.key == Key.DirectionRight) {
|
||||
player.seekForward()
|
||||
updateSkipIndicator(player.seekForwardIncrement)
|
||||
player.seekForward(seekForward)
|
||||
updateSkipIndicator(seekForward.inWholeMilliseconds)
|
||||
} else {
|
||||
controllerViewState.showControls()
|
||||
}
|
||||
|
|
@ -54,13 +59,13 @@ class PlaybackKeyHandler(
|
|||
}
|
||||
|
||||
Key.MediaFastForward, Key.MediaSkipForward -> {
|
||||
player.seekForward()
|
||||
updateSkipIndicator(player.seekForwardIncrement)
|
||||
player.seekForward(seekForward)
|
||||
updateSkipIndicator(seekForward.inWholeMilliseconds)
|
||||
}
|
||||
|
||||
Key.MediaRewind, Key.MediaSkipBackward -> {
|
||||
player.seekBack()
|
||||
updateSkipIndicator(-player.seekBackIncrement)
|
||||
player.seekBack(seekBack)
|
||||
updateSkipIndicator(-seekBack.inWholeMilliseconds)
|
||||
}
|
||||
|
||||
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.tv.material3.Text
|
||||
import org.jellyfin.sdk.model.api.TrickplayInfo
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Composable
|
||||
fun PlaybackOverlay(
|
||||
|
|
@ -29,6 +30,8 @@ fun PlaybackOverlay(
|
|||
previousEnabled: Boolean,
|
||||
nextEnabled: Boolean,
|
||||
seekEnabled: Boolean,
|
||||
seekBack: Duration,
|
||||
seekForward: Duration,
|
||||
onPlaybackActionClick: (PlaybackAction) -> Unit,
|
||||
onSeekBarChange: (Long) -> Unit,
|
||||
showDebugInfo: Boolean,
|
||||
|
|
@ -121,6 +124,8 @@ fun PlaybackOverlay(
|
|||
playbackSpeed = playbackSpeed,
|
||||
scale = scale,
|
||||
seekBarIntervals = 16,
|
||||
seekBack = seekBack,
|
||||
seekForward = seekForward,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,305 +0,0 @@
|
|||
package com.github.damontecres.dolphin.ui.preferences
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.ArrayRes
|
||||
import androidx.annotation.StringRes
|
||||
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 kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* A preference that can be stored in the shared preferences.
|
||||
*
|
||||
* @param T The type of the preference value.
|
||||
*/
|
||||
sealed interface AppPreference<T> {
|
||||
/**
|
||||
* String resource ID for the title of the preference
|
||||
*/
|
||||
@get:StringRes
|
||||
val title: Int
|
||||
|
||||
/**
|
||||
* Default value for the preference for UI purposes
|
||||
*/
|
||||
val defaultValue: T
|
||||
|
||||
/**
|
||||
* A function that gets the value from the [AppPreferences] object for UI purposes. This means
|
||||
* that it should return the value that is displayed in the UI, which isn't necessarily the raw value
|
||||
*/
|
||||
val getter: (prefs: AppPreferences) -> T
|
||||
|
||||
/**
|
||||
* A function that sets the value in the [AppPreferences] object from the UI. It should convert the value if needed
|
||||
*/
|
||||
val setter: (prefs: AppPreferences, value: T) -> AppPreferences
|
||||
|
||||
fun summary(
|
||||
context: Context,
|
||||
value: T?,
|
||||
): String? = null
|
||||
|
||||
fun validate(value: T): PreferenceValidation = PreferenceValidation.Valid
|
||||
|
||||
companion object {
|
||||
val SkipForward =
|
||||
AppSliderPreference(
|
||||
title = R.string.skip_forward_preference,
|
||||
defaultValue = 30,
|
||||
min = 10,
|
||||
max = 5.minutes.inWholeSeconds,
|
||||
interval = 5,
|
||||
getter = {
|
||||
it.playbackPreferences.skipForwardMs
|
||||
.milliseconds.inWholeSeconds
|
||||
},
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences {
|
||||
skipForwardMs = value.seconds.inWholeMilliseconds
|
||||
}
|
||||
},
|
||||
summarizer = { value ->
|
||||
if (value != null) {
|
||||
"$value seconds"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val SkipBack =
|
||||
AppSliderPreference(
|
||||
title = R.string.skip_back_preference,
|
||||
defaultValue = 10,
|
||||
min = 5,
|
||||
max = 5.minutes.inWholeSeconds,
|
||||
interval = 5,
|
||||
getter = {
|
||||
it.playbackPreferences.skipBackMs
|
||||
.milliseconds.inWholeSeconds
|
||||
},
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences {
|
||||
skipBackMs = value.seconds.inWholeMilliseconds
|
||||
}
|
||||
},
|
||||
summarizer = { value ->
|
||||
if (value != null) {
|
||||
"$value seconds"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// val GridJumpButtons =
|
||||
// AppSwitchPreference(
|
||||
// title = R.string.show_grid_jump_buttons,
|
||||
// defaultValue = true,
|
||||
// getter = { it.interfacePreferences.showGridJumpButtons },
|
||||
// setter = { prefs, value ->
|
||||
// prefs.updateInterfacePreferences { showGridJumpButtons = value }
|
||||
// },
|
||||
// summaryOn = R.string.enabled,
|
||||
// summaryOff = R.string.disabled,
|
||||
// )
|
||||
|
||||
// val ShowGridFooter =
|
||||
// AppSwitchPreference(
|
||||
// title = R.string.grid_position_footer,
|
||||
// defaultValue = true,
|
||||
// getter = { it.interfacePreferences.showPositionFooter },
|
||||
// setter = { prefs, value ->
|
||||
// prefs.updateInterfacePreferences { showPositionFooter = value }
|
||||
// },
|
||||
// summaryOn = R.string.show,
|
||||
// summaryOff = R.string.hide,
|
||||
// )
|
||||
|
||||
val ControllerTimeout =
|
||||
AppSliderPreference(
|
||||
title = R.string.hide_controller_timeout,
|
||||
defaultValue = 5000,
|
||||
min = 500,
|
||||
max = 15.seconds.inWholeMilliseconds,
|
||||
interval = 100,
|
||||
getter = { it.playbackPreferences.controllerTimeoutMs },
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences { controllerTimeoutMs = value }
|
||||
},
|
||||
summarizer = { value -> value?.let { "${value / 1000.0} seconds" } },
|
||||
)
|
||||
|
||||
val SeekBarSteps =
|
||||
AppSliderPreference(
|
||||
title = R.string.seek_bar_steps,
|
||||
defaultValue = 16,
|
||||
min = 4,
|
||||
max = 64,
|
||||
interval = 1,
|
||||
getter = { it.playbackPreferences.seekBarSteps.toLong() },
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences { seekBarSteps = value.toInt() }
|
||||
},
|
||||
summarizer = { value -> value?.toString() },
|
||||
)
|
||||
|
||||
// val PlaybackDebugInfo =
|
||||
// AppSwitchPreference(
|
||||
// title = R.string.playback_debug_info,
|
||||
// prefKey = R.string.pref_key_show_playback_debug_info,
|
||||
// defaultValue = false,
|
||||
// getter = { it.playbackPreferences.showDebugInfo },
|
||||
// setter = { prefs, value ->
|
||||
// prefs.updatePlaybackPreferences { showDebugInfo = value }
|
||||
// },
|
||||
// summaryOn = R.string.show,
|
||||
// summaryOff = R.string.hide,
|
||||
// )
|
||||
|
||||
// val AutoCheckForUpdates =
|
||||
// AppSwitchPreference(
|
||||
// title = R.string.check_for_updates,
|
||||
// prefKey = R.string.pref_key_auto_check_updates,
|
||||
// defaultValue = true,
|
||||
// getter = { it.updatePreferences.checkForUpdates },
|
||||
// setter = { prefs, value ->
|
||||
// prefs.updateUpdatePreferences { checkForUpdates = value }
|
||||
// },
|
||||
// summaryOn = R.string.enabled,
|
||||
// summaryOff = R.string.disabled,
|
||||
// )
|
||||
|
||||
// val UpdateUrl =
|
||||
// AppStringPreference(
|
||||
// title = R.string.update_url,
|
||||
// defaultValue = "",
|
||||
// getter = { it.updatePreferences.updateUrl },
|
||||
// setter = { prefs, value ->
|
||||
// prefs.updateUpdatePreferences { updateUrl = value }
|
||||
// },
|
||||
// summary = R.string.update_url_summary,
|
||||
// )
|
||||
|
||||
// val OssLicenseInfo =
|
||||
// AppDestinationPreference(
|
||||
// title = R.string.oss_license_info,
|
||||
// destination = Destination.LicenseInfo,
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
data class AppSwitchPreference(
|
||||
@get:StringRes override val title: Int,
|
||||
override val defaultValue: Boolean,
|
||||
override val getter: (prefs: AppPreferences) -> Boolean,
|
||||
override val setter: (prefs: AppPreferences, value: Boolean) -> AppPreferences,
|
||||
val validator: (value: Boolean) -> PreferenceValidation = { PreferenceValidation.Valid },
|
||||
@param:StringRes val summary: Int? = null,
|
||||
@param:StringRes val summaryOn: Int? = null,
|
||||
@param:StringRes val summaryOff: Int? = null,
|
||||
) : AppPreference<Boolean> {
|
||||
override fun summary(
|
||||
context: Context,
|
||||
value: Boolean?,
|
||||
): String? =
|
||||
when {
|
||||
summaryOn != null && value == true -> context.getString(summaryOn)
|
||||
summaryOff != null && value == false -> context.getString(summaryOff)
|
||||
else -> summary?.let { context.getString(summary) }
|
||||
}
|
||||
}
|
||||
|
||||
open class AppStringPreference(
|
||||
@param:StringRes override val title: Int,
|
||||
override val defaultValue: String,
|
||||
override val getter: (AppPreferences) -> String,
|
||||
override val setter: (AppPreferences, String) -> AppPreferences,
|
||||
@param:StringRes val summary: Int?,
|
||||
) : AppPreference<String> {
|
||||
override fun summary(
|
||||
context: Context,
|
||||
value: String?,
|
||||
): String? = summary?.let { context.getString(it) } ?: value
|
||||
}
|
||||
|
||||
data class AppChoicePreference<T>(
|
||||
@param:StringRes override val title: Int,
|
||||
override val defaultValue: T,
|
||||
@param:ArrayRes val displayValues: Int,
|
||||
val indexToValue: (index: Int) -> T,
|
||||
val valueToIndex: (T) -> Int,
|
||||
override val getter: (prefs: AppPreferences) -> T,
|
||||
override val setter: (prefs: AppPreferences, value: T) -> AppPreferences,
|
||||
@param:StringRes val summary: Int? = null,
|
||||
) : AppPreference<T>
|
||||
|
||||
data class AppMultiChoicePreference<T>(
|
||||
@param:StringRes override val title: Int,
|
||||
override val defaultValue: List<T>,
|
||||
val allValues: List<T>,
|
||||
@param:ArrayRes val displayValues: Int,
|
||||
override val getter: (prefs: AppPreferences) -> List<T>,
|
||||
override val setter: (prefs: AppPreferences, value: List<T>) -> AppPreferences,
|
||||
@param:StringRes val summary: Int? = null,
|
||||
val toSharedPrefs: (T) -> String,
|
||||
val fromSharedPrefs: (String) -> T?,
|
||||
) : AppPreference<List<T>>
|
||||
|
||||
data class AppClickablePreference(
|
||||
@param:StringRes override val title: Int,
|
||||
override val defaultValue: Unit = Unit,
|
||||
override val getter: (prefs: AppPreferences) -> Unit = { },
|
||||
override val setter: (prefs: AppPreferences, value: Unit) -> AppPreferences = { prefs, _ -> prefs },
|
||||
@param:StringRes val summary: Int? = null,
|
||||
) : AppPreference<Unit> {
|
||||
override fun summary(
|
||||
context: Context,
|
||||
value: Unit?,
|
||||
): String? = summary?.let { context.getString(it) }
|
||||
}
|
||||
|
||||
data class AppDestinationPreference(
|
||||
@param:StringRes override val title: Int,
|
||||
override val defaultValue: Unit = Unit,
|
||||
override val getter: (prefs: AppPreferences) -> Unit = { },
|
||||
override val setter: (prefs: AppPreferences, value: Unit) -> AppPreferences = { prefs, _ -> prefs },
|
||||
@param:StringRes val summary: Int? = null,
|
||||
val destination: Destination,
|
||||
) : AppPreference<Unit> {
|
||||
override fun summary(
|
||||
context: Context,
|
||||
value: Unit?,
|
||||
): String? = summary?.let { context.getString(it) }
|
||||
}
|
||||
|
||||
class AppSliderPreference(
|
||||
@param:StringRes override val title: Int,
|
||||
override val defaultValue: Long,
|
||||
/**
|
||||
* Minimum value for the slider. Similar to [defaultValue], this is for UI purposes only
|
||||
*/
|
||||
val min: Long = 0,
|
||||
/**
|
||||
* Max value for the slider. Similar to [defaultValue], this is for UI purposes only
|
||||
*/
|
||||
val max: Long = 100,
|
||||
val interval: Int = 1,
|
||||
override val getter: (prefs: AppPreferences) -> Long,
|
||||
override val setter: (prefs: AppPreferences, value: Long) -> AppPreferences,
|
||||
@param:StringRes val summary: Int? = null,
|
||||
val summarizer: ((Long?) -> String?)? = null,
|
||||
) : AppPreference<Long> {
|
||||
override fun summary(
|
||||
context: Context,
|
||||
value: Long?,
|
||||
): String? =
|
||||
summarizer?.invoke(value)
|
||||
?: summary?.let { context.getString(it) }
|
||||
?: value?.toString()
|
||||
}
|
||||
|
|
@ -38,6 +38,14 @@ import androidx.tv.material3.Switch
|
|||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
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.DialogParams
|
||||
import com.github.damontecres.dolphin.ui.components.DialogPopup
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.damontecres.dolphin.ui.preferences
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.github.damontecres.dolphin.preferences.AppPreference
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
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.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.nav.NavigationManager
|
||||
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 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(
|
||||
val version: String,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.ProvideTextStyle
|
||||
import androidx.tv.material3.contentColorFor
|
||||
import com.github.damontecres.dolphin.preferences.AppSliderPreference
|
||||
import com.github.damontecres.dolphin.ui.components.SliderBar
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue