Fix focus issues & add clear image cache option

This commit is contained in:
Damontecres 2025-10-12 22:31:09 -04:00
parent 370cd2c146
commit 4113f990b2
No known key found for this signature in database
8 changed files with 50 additions and 22 deletions

View file

@ -439,6 +439,13 @@ sealed interface AppPreference<T> {
indexToValue = { SkipSegmentBehavior.forNumber(it) },
valueToIndex = { it.number },
)
val ClearImageCache =
AppClickablePreference(
title = R.string.clear_image_cache,
getter = { },
setter = { prefs, _ -> prefs },
)
}
}
@ -513,6 +520,7 @@ val advancedPreferences =
title = R.string.more,
preferences =
listOf(
AppPreference.ClearImageCache,
AppPreference.OssLicenseInfo,
),
),

View file

@ -15,7 +15,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.FocusState
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
@ -29,7 +28,6 @@ import androidx.tv.material3.Text
import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.ui.PreviewTvSpec
import com.github.damontecres.dolphin.ui.theme.DolphinTheme
import com.github.damontecres.dolphin.ui.tryRequestFocus
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@ -139,14 +137,10 @@ fun ExpandablePlayButtons(
modifier =
modifier
.focusGroup()
.focusProperties {
onEnter = {
firstFocus.tryRequestFocus()
}
},
.focusRestorer(firstFocus),
) {
if (resumePosition > Duration.ZERO) {
item {
item("play") {
ExpandablePlayButton(
R.string.resume,
resumePosition,
@ -157,7 +151,7 @@ fun ExpandablePlayButtons(
.focusRequester(firstFocus),
)
}
item {
item("restart") {
ExpandablePlayButton(
R.string.restart,
Duration.ZERO,
@ -168,7 +162,7 @@ fun ExpandablePlayButtons(
)
}
} else {
item {
item("play") {
ExpandablePlayButton(
R.string.play,
Duration.ZERO,
@ -182,7 +176,7 @@ fun ExpandablePlayButtons(
}
// Watched button
item {
item("watched") {
ExpandableFaButton(
title = if (watched) R.string.mark_unwatched else R.string.mark_watched,
iconStringRes = if (watched) R.string.fa_eye else R.string.fa_eye_slash,
@ -192,7 +186,7 @@ fun ExpandablePlayButtons(
}
// More button
item {
item("more") {
ExpandablePlayButton(
R.string.more,
Duration.ZERO,

View file

@ -50,6 +50,8 @@ fun FocusedEpisodeHeader(
Text(
text = dto.episodeTitle ?: dto.name ?: "",
style = MaterialTheme.typography.headlineSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier,
)
Row(

View file

@ -16,7 +16,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableIntStateOf
@ -34,6 +33,7 @@ import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
@ -77,8 +77,6 @@ fun SeriesOverviewContent(
val tabRowFocusRequester = remember { FocusRequester() }
val focusedEpisode = episodes.getOrNull(position.episodeRowIndex)
LaunchedEffect(position) {
}
Box(
modifier =
@ -184,6 +182,8 @@ fun SeriesOverviewContent(
Text(
text = it,
style = MaterialTheme.typography.headlineMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier,
)
}

View file

@ -249,12 +249,16 @@ fun MainPageHeader(
Text(
text = title,
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
subtitle?.let {
Text(
text = subtitle,
style = MaterialTheme.typography.headlineSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
if (details.isNotEmpty()) {

View file

@ -280,10 +280,11 @@ class PlaybackViewModel
Timber.i("No change in playback for changeStreams")
return
}
// TODO if the new audio or subtitle index is already in the streams (eg direct play), should toggle in the player instead
val maxBitrate =
preferences.appPreferences.playbackPreferences.maxBitrate
.takeIf { it > 0 } ?: AppPreference.DEFAULT_BITRATE
val response =
val response by
api.mediaInfoApi
.getPostedPlaybackInfo(
itemId,
@ -302,7 +303,7 @@ class PlaybackViewModel
alwaysBurnInSubtitleWhenTranscoding = null,
maxStreamingBitrate = maxBitrate.toInt(),
),
).content
)
val source = response.mediaSources.firstOrNull()
source?.let { source ->
val mediaUrl =
@ -335,8 +336,8 @@ class PlaybackViewModel
?.let {
it.deliveryUrl?.let { deliveryUrl ->
var flags = 0
if (it.isForced) flags = flags.and(C.SELECTION_FLAG_FORCED)
if (it.isDefault) flags = flags.and(C.SELECTION_FLAG_DEFAULT)
if (it.isForced) flags = flags.or(C.SELECTION_FLAG_FORCED)
if (it.isDefault) flags = flags.or(C.SELECTION_FLAG_DEFAULT)
MediaItem.SubtitleConfiguration
.Builder(
api.createUrl(deliveryUrl).toUri(),
@ -368,6 +369,7 @@ class PlaybackViewModel
)
withContext(Dispatchers.Main) {
// TODO, don't need to release & recreate when switching streams
this@PlaybackViewModel.activityListener?.let {
it.release()
player.removeListener(it)
@ -389,7 +391,7 @@ class PlaybackViewModel
positionMs,
)
if (audioIndex != null || subtitleIndex != null) {
val trackActivationListener =
val onTracksChangedListener =
object : Player.Listener {
override fun onTracksChanged(tracks: Tracks) {
Timber.v("onTracksChanged: $tracks")
@ -408,7 +410,7 @@ class PlaybackViewModel
}
}
}
player.addListener(trackActivationListener)
player.addListener(onTracksChangedListener)
}
}
val trickPlayInfo =
@ -416,7 +418,7 @@ class PlaybackViewModel
?.get(source.id)
?.values
?.firstOrNull()
Timber.v("Trickplay info: $trickPlayInfo")
// Timber.v("Trickplay info: $trickPlayInfo")
withContext(Dispatchers.Main) {
trickplay.value = trickPlayInfo
}

View file

@ -39,6 +39,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import androidx.tv.material3.surfaceColorAtElevation
import coil3.SingletonImageLoader
import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.preferences.AppPreference
import com.github.damontecres.dolphin.preferences.AppPreferences
@ -245,6 +246,22 @@ fun PreferencesContent(
)
}
AppPreference.ClearImageCache -> {
ClickPreference(
title = stringResource(pref.title),
onClick = {
SingletonImageLoader.get(context).let {
it.memoryCache?.clear()
it.diskCache?.clear()
}
},
modifier = Modifier,
summary = null,
onLongClick = {},
interactionSource = interactionSource,
)
}
else -> {
val value = pref.getter.invoke(preferences)
ComposablePreference(

View file

@ -74,5 +74,6 @@
<string name="update_url_summary">URL used to check for app updates</string>
<string name="updates">Updates</string>
<string name="no_update_available">No update available</string>
<string name="clear_image_cache">Clear image cache</string>
</resources>