Handle density configuration changes to prevent unnecessary app restarts (#1828)

## Description
Add `density` to `configChanges` and handle any changes to density that
occur. Thankfully, Compose handles density changes itself, so only
changes where a density calculation is "remembered" needed to be
updated.

Without this, some devices will recreate `MainActivity` when switching
resolutions for playback. That recreation would cause playback to end
immediately.

### Related issues
Fixes #1809

### Testing
Tested on Onn 4k Pro & nvidia shield

I was able to reproduce on the Onn, but not the Shield, so this might be
device and/or API level specific

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-08-12 21:01:28 -04:00 • committed by GitHub
parent e6304f79a2
commit 54966f9124
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 31 additions and 12 deletions

View file

@ -51,7 +51,7 @@
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout|smallestScreenSize">
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout|smallestScreenSize|density">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View file

@ -380,7 +380,7 @@ class MainActivity : AppCompatActivity() {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Timber.d("onConfigurationChanged")
Timber.d("onConfigurationChanged: newConfig=%s", newConfig)
}
override fun onNewIntent(intent: Intent) {

View file

@ -73,7 +73,7 @@ fun BannerCard(
val imageUrlService = LocalImageUrlService.current
val density = LocalDensity.current
val fillHeight =
remember(cardHeight) {
remember(cardHeight, density) {
if (cardHeight.isSpecified) {
with(density) {
cardHeight.roundToPx()

View file

@ -49,7 +49,7 @@ fun ChapterCard(
val density = LocalDensity.current
val imageUrlService = LocalImageUrlService.current
val imageUrl =
remember(chapter, cardHeight) {
remember(chapter, cardHeight, density) {
imageUrlService.getItemImageUrl(
itemId = chapter.itemId,
imageType = ImageType.CHAPTER,

View file

@ -114,7 +114,7 @@ fun CollectionFolderGrid(
columns = viewOptions.columns,
spacing = viewOptions.spacing.dp,
bringIntoViewSpec =
remember(viewOptions) {
remember(viewOptions, density) {
val spacingPx = with(density) { viewOptions.spacing.dp.toPx() }
if (viewOptions.showDetails) {
ScrollToTopBringIntoViewSpec(spacingPx)

View file

@ -275,7 +275,7 @@ fun GenreCardGrid(
val density = LocalDensity.current
val configuration = LocalConfiguration.current
val cardWidthPx =
remember {
remember(density) {
with(density) {
// Grid has 16dp padding on either side & 16dp spacing between 4 cards
// This isn't exact though because it doesn't account for nav drawer or letters, but it's close and the calculation is much faster

View file

@ -166,7 +166,7 @@ fun StudioCardGrid(
val density = LocalDensity.current
val configuration = LocalConfiguration.current
val cardWidthPx =
remember {
remember(density) {
with(density) {
// Grid has 16dp padding on either side & 16dp spacing between 4 cards
// This isn't exact though because it doesn't account for nav drawer or letters, but it's close and the calculation is much faster

View file

@ -796,7 +796,7 @@ fun PlaylistItem(
val focused by interactionSource.collectIsFocusedAsState()
val imageWidth = 160.dp
val density = LocalDensity.current
val imageWidthPx = remember(imageWidth) { with(density) { imageWidth.roundToPx() } }
val imageWidthPx = remember(imageWidth, density) { with(density) { imageWidth.roundToPx() } }
ListItem(
selected = false,
onClick = onClick,

View file

@ -76,7 +76,7 @@ fun CollectionMixedGrid(
columns = cardViewOptions.columns,
spacing = cardViewOptions.spacing.dp,
bringIntoViewSpec =
remember(cardViewOptions) {
remember(cardViewOptions, density) {
val spacingPx = with(density) { cardViewOptions.spacing.dp.toPx() }
if (cardViewOptions.showDetails) {
ScrollToTopBringIntoViewSpec(spacingPx)

View file

@ -165,6 +165,7 @@ fun PlaybackPageContent(
val prefs = preferences.appPreferences.playbackPreferences
val scope = rememberCoroutineScope()
val density = LocalDensity.current
LaunchedEffect(density) { viewModel.updateDensity(density) }
val userDto by viewModel.currentUserDto.collectAsState()
var showDebugInfo by remember { mutableStateOf(prefs.showDebugInfo) }
@ -504,7 +505,9 @@ fun PlaybackPageContent(
val subtitleMaxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f)
val isImageSubtitles =
remember(state.subtitleCues) { state.subtitleCues.firstOrNull()?.bitmap != null }
var cueCount by remember { mutableIntStateOf(0) }
// Reset cueCount when density changes to force re-applying subtitle style
var cueCount by remember(density) { mutableIntStateOf(0) }
val subtitleVisible =
skipIndicatorDuration == 0L &&
@ -540,6 +543,7 @@ fun PlaybackPageContent(
}
subtitleView.setCues(state.subtitleCues)
if (state.subtitleCues.size > cueCount) {
Timber.i("Applying subtitle style to SubtitleView")
// The output creates a painter for each cue, so need to apply the changes when the number of cues increases
Media3SubtitleOverride(subtitleSettings.calculateEdgeSize(density))
.apply(subtitleView)

View file

@ -1636,6 +1636,21 @@ class PlaybackViewModel
}
}
fun updateDensity(density: Density) {
Timber.d("Density changed")
viewModelScope.launchDefault {
val availableCommands = onMain { player.availableCommands }
if (availableCommands.contains(Player.COMMAND_PREPARE) && player is MpvPlayer) {
Timber.i("Applying density change for subtitle config to MPV")
val configuration = context.resources.configuration
preferences.appPreferences.interfacePreferences.subtitlesPreferences.applyToMpv(
configuration,
density,
)
}
}
}
override fun onBandwidthEstimate(
eventTime: AnalyticsListener.EventTime,
totalLoadTimeMs: Int,

View file

@ -100,11 +100,11 @@ fun PlaybackOverlay(
val density = LocalDensity.current
val titleHeight =
remember(item?.title) {
remember(item?.title, density) {
if (item?.title.isNotNullOrBlank()) with(density) { titleTextSize.toDp() } else 0.dp
}
val subtitleHeight =
remember(item?.subtitleLong) {
remember(item?.subtitleLong, density) {
if (item?.subtitleLong.isNotNullOrBlank()) with(density) { subtitleTextSize.toDp() } else 0.dp
}