From bf8e11ae1aaf6986b6b268f4a23f1ee1f800ebb3 Mon Sep 17 00:00:00 2001 From: davotoula Date: Wed, 30 Sep 2026 11:44:44 +0200 Subject: [PATCH] fix: stop the media dialog clobbering its window attributes every frame ZoomableImageDialog copied the activity window's attributes onto the dialog window inline in composition. That content recomposes on every frame of a pager swipe (it reads the image bounds the pager moves), so a swipe sent ~60 attribute updates to the window manager, and each one overwrote the dialog's own colorMode and desiredHdrHeadroom with the feed's. On a Pixel 9a that capped fullscreen HDR photos at the feed's 2x, kept an SDR page in HDR mode (ratio 5), and made swiping back to an HDR photo land in SDR depending on timing. Run the copy in an effect keyed on orientation and the two windows, and preserve the dialog's colorMode and HDR headroom alongside the brightness override it already kept. Measured after: HDR page 5, SDR page 1, HDR again 5, stable across repeated swipes. --- .../ui/components/ZoomableContentDialog.kt | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt index 53ecdf75f5..7a010cfe21 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.ui.components import android.content.Context +import android.os.Build import android.os.Handler import android.os.Looper import android.view.WindowManager @@ -44,6 +45,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue @@ -182,20 +184,29 @@ fun ZoomableImageDialog( val activityWindow = getActivityWindow() val dialogWindow = getDialogWindow() - if (activityWindow != null && dialogWindow != null) { - // Preserve any brightness override already applied to the dialog window (e.g. by the - // fullscreen swipe controls). This block re-runs on recomposition (orientation change - // re-reads `orientation` above), and copying the activity attributes would otherwise - // reset screenBrightness and snap the user's brightness back mid-session. - val currentBrightness = dialogWindow.attributes.screenBrightness - val attributes = WindowManager.LayoutParams() - attributes.copyFrom(activityWindow.attributes) - attributes.type = dialogWindow.attributes.type - // Disable the system dim so the thumbnail stays visible behind the growing dialog. - attributes.dimAmount = 0f - attributes.flags = attributes.flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv() - attributes.screenBrightness = currentBrightness - dialogWindow.attributes = attributes + // Keyed, not inline: this content recomposes on every frame of a pager swipe (the image + // bounds it reads move), and each assignment below is a window-manager round trip. + DisposableEffect(orientation, activityWindow, dialogWindow) { + if (activityWindow != null && dialogWindow != null) { + // Preserve what the dialog window owns: the brightness override applied by the + // fullscreen swipe controls, and the HDR mode [RequestHdrFor] manages for the + // image on screen. Copying the activity's instead would snap brightness back + // mid-session and cap or force HDR by whatever the feed behind happens to show. + val current = dialogWindow.attributes + val attributes = WindowManager.LayoutParams() + attributes.copyFrom(activityWindow.attributes) + attributes.type = current.type + // Disable the system dim so the thumbnail stays visible behind the growing dialog. + attributes.dimAmount = 0f + attributes.flags = attributes.flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv() + attributes.screenBrightness = current.screenBrightness + attributes.colorMode = current.colorMode + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + attributes.desiredHdrHeadroom = current.desiredHdrHeadroom + } + dialogWindow.attributes = attributes + } + onDispose {} } // Go fully immersive while the full-screen media viewer is open. Applies to full-screen