Few UI tweaks (#325)

- Move loading indicator to center of grid when changing filters so it's
more obvious
- Move 'remove' filter to top for the first filter dropdown for
consistency
- Use theme color for download progress indicator
- Show playback controls at the end of playing a video if there's
nothing up next
This commit is contained in:
damontecres 2025-11-25 16:36:33 -05:00 committed by GitHub
parent 0bfe3d2653
commit c0237a6123
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 58 additions and 44 deletions

View file

@ -21,7 +21,10 @@ fun CircularProgress(modifier: Modifier = Modifier) {
Box(modifier = modifier) { Box(modifier = modifier) {
CircularProgressIndicator( CircularProgressIndicator(
color = MaterialTheme.colorScheme.border, color = MaterialTheme.colorScheme.border,
modifier = Modifier.align(Alignment.Center), modifier =
Modifier
.fillMaxSize()
.align(Alignment.Center),
) )
} }
} }

View file

@ -571,7 +571,7 @@ fun CollectionFolderGrid(
backgroundLoading == LoadingState.Loading, backgroundLoading == LoadingState.Loading,
modifier = modifier =
Modifier Modifier
.align(Alignment.BottomEnd) .align(Alignment.Center)
.padding(16.dp), .padding(16.dp),
) { ) {
CircularProgress( CircularProgress(

View file

@ -78,31 +78,7 @@ fun FilterByButton(
nestedDropDown = null nestedDropDown = null
}, },
) { ) {
filterOptions
.forEach { filterOption ->
val currentValue = remember(current) { filterOption.get(current) }
TvDropdownMenuItem(
leadingIcon = {
if (currentValue != null) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Filter active",
)
}
},
text = {
Text(
text = stringResource(filterOption.stringRes),
)
},
onClick = {
nestedDropDown = filterOption
},
modifier = Modifier,
)
}
if (filterCount > 0) { if (filterCount > 0) {
HorizontalDivider()
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val focused by interactionSource.collectIsFocusedAsState() val focused by interactionSource.collectIsFocusedAsState()
TvDropdownMenuItem( TvDropdownMenuItem(
@ -126,7 +102,35 @@ fun FilterByButton(
interactionSource = interactionSource, interactionSource = interactionSource,
modifier = Modifier, modifier = Modifier,
) )
HorizontalDivider()
} }
filterOptions
.forEachIndexed { index, filterOption ->
val focusRequester = remember { FocusRequester() }
if (index == 0) {
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
}
val currentValue = remember(current) { filterOption.get(current) }
TvDropdownMenuItem(
leadingIcon = {
if (currentValue != null) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Filter active",
)
}
},
text = {
Text(
text = stringResource(filterOption.stringRes),
)
},
onClick = {
nestedDropDown = filterOption
},
modifier = Modifier.focusRequester(focusRequester),
)
}
} }
DropdownMenu( DropdownMenu(

View file

@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.debounce
*/ */
class ControllerViewState internal constructor( class ControllerViewState internal constructor(
@param:IntRange(from = 0) @param:IntRange(from = 0)
private val hideMilliseconds: Long, var hideMilliseconds: Long,
val controlsEnabled: Boolean, val controlsEnabled: Boolean,
) { ) {
private val channel = Channel<Long>(CONFLATED) private val channel = Channel<Long>(CONFLATED)

View file

@ -167,17 +167,8 @@ fun PlaybackPage(
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
focusRequester.tryRequestFocus() focusRequester.tryRequestFocus()
} }
val controllerViewState = val controllerViewState = remember { viewModel.controllerViewState }
remember {
ControllerViewState(
preferences.appPreferences.playbackPreferences.controllerTimeoutMs,
true,
)
}.also {
LaunchedEffect(it) {
it.observe()
}
}
var skipIndicatorDuration by remember { mutableLongStateOf(0L) } var skipIndicatorDuration by remember { mutableLongStateOf(0L) }
LaunchedEffect(controllerViewState.controlsVisible) { LaunchedEffect(controllerViewState.controlsVisible) {
// If controller shows/hides, immediately cancel the skip indicator // If controller shows/hides, immediately cancel the skip indicator

View file

@ -128,6 +128,12 @@ class PlaybackViewModel
} }
internal val mutex = Mutex() internal val mutex = Mutex()
val controllerViewState =
ControllerViewState(
AppPreference.ControllerTimeout.defaultValue,
true,
)
val loading = MutableLiveData<LoadingState>(LoadingState.Loading) val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
val currentMediaInfo = MutableLiveData<CurrentMediaInfo>(CurrentMediaInfo.EMPTY) val currentMediaInfo = MutableLiveData<CurrentMediaInfo>(CurrentMediaInfo.EMPTY)
@ -153,6 +159,7 @@ class PlaybackViewModel
val subtitleSearchLanguage = MutableLiveData<String>(Locale.current.language) val subtitleSearchLanguage = MutableLiveData<String>(Locale.current.language)
init { init {
viewModelScope.launch(ExceptionHandler()) { controllerViewState.observe() }
player.addListener(this) player.addListener(this)
(player as? ExoPlayer)?.addAnalyticsListener(this) (player as? ExoPlayer)?.addAnalyticsListener(this)
addCloseable { player.removeListener(this@PlaybackViewModel) } addCloseable { player.removeListener(this@PlaybackViewModel) }
@ -178,6 +185,8 @@ class PlaybackViewModel
) { ) {
nextUp.value = null nextUp.value = null
this.preferences = preferences this.preferences = preferences
controllerViewState.hideMilliseconds =
preferences.appPreferences.playbackPreferences.controllerTimeoutMs
this.deviceProfile = deviceProfile this.deviceProfile = deviceProfile
this.forceTranscoding = this.forceTranscoding =
(destination as? Destination.Playback)?.forceTranscoding ?: false (destination as? Destination.Playback)?.forceTranscoding ?: false
@ -696,6 +705,9 @@ class PlaybackViewModel
Timber.v("Setting next up to ${nextItem?.id}") Timber.v("Setting next up to ${nextItem?.id}")
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
nextUp.value = nextItem nextUp.value = nextItem
if (nextItem == null) {
controllerViewState.showControls()
}
} }
} }
} }

View file

@ -360,15 +360,19 @@ fun DownloadDialog(
if (progress != null) { if (progress != null) {
CircularProgressIndicator( CircularProgressIndicator(
progress = { progress }, progress = { progress },
Modifier color = MaterialTheme.colorScheme.border,
.size(48.dp) modifier =
.padding(8.dp), Modifier
.size(48.dp)
.padding(8.dp),
) )
} else { } else {
CircularProgressIndicator( CircularProgressIndicator(
Modifier color = MaterialTheme.colorScheme.border,
.size(48.dp) modifier =
.padding(8.dp), Modifier
.size(48.dp)
.padding(8.dp),
) )
} }
} }