@@ -40,12 +40,13 @@ import androidx.compose.ui.layout.Measurable
4040import androidx.compose.ui.layout.MeasureResult
4141import androidx.compose.ui.layout.MeasureScope
4242import androidx.compose.ui.layout.layout
43- import androidx.compose.ui.platform.LocalWindowInfo
4443import androidx.compose.ui.unit.Constraints
4544import androidx.compose.ui.unit.IntSize
4645import com.tunjid.composables.ui.animate
4746import com.tunjid.heron.data.files.RestrictedFile
4847import com.tunjid.heron.data.files.uiDisplayModel
48+ import com.tunjid.heron.media.LocalMediaConfig
49+ import com.tunjid.heron.media.MediaConfig
4950import com.tunjid.heron.ui.shapes.RoundedPolygonShape
5051import com.tunjid.heron.ui.shapes.animate
5152import kotlin.math.min
@@ -107,12 +108,10 @@ data class ImageArgs(
107108
108109@Stable
109110class ImageState internal constructor(
110- args : ImageArgs ,
111- private val imageLoader : ImageLoader ,
112- private val windowSize : () -> IntSize ,
111+ internal val args : () -> ImageArgs ,
112+ internal val mediaConfig : () -> MediaConfig ,
113+ private val imageLoader : () -> ImageLoader ,
113114) {
114- var args by mutableStateOf(args)
115-
116115 internal var image by mutableStateOf<Image ?>(null )
117116 private var layoutSize by mutableStateOf(IntSize .Zero )
118117
@@ -152,7 +151,7 @@ class ImageState internal constructor(
152151 )
153152 .distinctUntilChanged()
154153 .collectLatest { (request, size) ->
155- imageLoader.fetchImage(
154+ imageLoader() .fetchImage(
156155 request = request,
157156 size = size,
158157 )
@@ -161,7 +160,7 @@ class ImageState internal constructor(
161160 }
162161
163162 private fun requests (): Flow <ImageRequest > =
164- snapshotFlow { args.request }
163+ snapshotFlow { args() .request }
165164
166165 private fun fetchSizes (): Flow <IntSize > =
167166 snapshotFlow { layoutSize }
@@ -171,23 +170,23 @@ class ImageState internal constructor(
171170 if (index == 0 ) 0 .milliseconds
172171 else ImageLayoutSizeRefetchDebounce .milliseconds
173172 }
174- .map { (_, size) -> size .bucketedFetchSize(windowSize() ) }
173+ .map { (_, size) -> mediaConfig() .bucketedFetchSize(size ) }
175174}
176175
177176@Composable
178177fun rememberUpdatedImageState (
179178 args : ImageArgs ,
180179): ImageState {
181- val imageLoader = LocalImageLoader .current
182- val windowSize = rememberUpdatedState(LocalWindowInfo .current.containerSize)
183- return remember(imageLoader) {
180+ val updatedArgs = rememberUpdatedState(args)
181+ val imageLoader = rememberUpdatedState(LocalImageLoader .current)
182+ val mediaConfig = rememberUpdatedState(LocalMediaConfig .current)
183+ return remember {
184184 ImageState (
185- args = args ,
186- imageLoader = imageLoader,
187- windowSize = windowSize ::value,
185+ mediaConfig = mediaConfig::value ,
186+ imageLoader = imageLoader::value ,
187+ args = updatedArgs ::value,
188188 )
189189 }
190- .also { it.args = args }
191190}
192191
193192fun ImageArgs (
@@ -248,14 +247,14 @@ fun AsyncImage(
248247 """ .trimIndent()
249248 }
250249
251- val contentDescription = state.args.contentDescription
250+ val contentDescription = state.args() .contentDescription
252251 val contentScaleState = rememberUpdatedState(
253- state.args.contentScale.animate(ImageInterpolationSpec ),
252+ state.args() .contentScale.animate(ImageInterpolationSpec ),
254253 )
255254 val alignmentState = rememberUpdatedState(
256- state.args.alignment.animate(ImageInterpolationSpec ),
255+ state.args() .alignment.animate(ImageInterpolationSpec ),
257256 )
258- val shape = state.args.shape.animate(ImageInterpolationSpec )
257+ val shape = state.args() .shape.animate(ImageInterpolationSpec )
259258
260259 Box (
261260 modifier = modifier
@@ -279,7 +278,9 @@ fun AsyncImage(
279278 contentScale = contentScaleState.value,
280279 )
281280
282- state.image?.AnimationEffect ()
281+ if (state.mediaConfig().autoPlayGifs()) {
282+ state.image?.AnimationEffect ()
283+ }
283284 }
284285
285286 val scope = rememberCoroutineScope(
@@ -307,46 +308,31 @@ private val IntSize.isUsable: Boolean
307308 height > IntSize .Zero .height &&
308309 height < Int .MAX_VALUE
309310
310- private fun IntSize .bucketedFetchSize (
311- windowSize : IntSize ,
311+ private fun MediaConfig .bucketedFetchSize (
312+ size : IntSize ,
312313): IntSize {
313- val maxWidth = windowSize.width.takeIf { it > 0 } ? : width
314- val maxHeight = windowSize.height.takeIf { it > 0 } ? : height
314+ val windowSize = windowSize()
315+ val maxWidth = if (windowSize.width > 0 ) windowSize.width else size.width
316+ val maxHeight = if (windowSize.height > 0 ) windowSize.height else size.height
315317 return IntSize (
316- width = min(width, maxWidth)
317- .roundedUpToDecodeBucket()
318+ width = roundedUpToDecodeBucket(min(size.width, maxWidth))
318319 .coerceAtMost(maxWidth),
319- height = min(height, maxHeight)
320- .roundedUpToDecodeBucket()
320+ height = roundedUpToDecodeBucket(min(size.height, maxHeight))
321321 .coerceAtMost(maxHeight),
322322 )
323323}
324324
325- private fun Int.roundedUpToDecodeBucket (): Int {
326- DecodeBucketsPx
327- // Smallest bucket >= this, via a lower-bound binary search over the ascending buckets. If this is
328- // larger than every bucket, keep the requested size (never upscale); the window cap still applies.
329- var low = 0
330- var high = DecodeBucketsPx .size - 1
331- var ceiling = this
332- while (low <= high) {
333- val mid = (low + high) ushr 1
334- val bucket = DecodeBucketsPx [mid]
335- if (bucket >= this ) {
336- ceiling = bucket
337- high = mid - 1
338- } else {
339- low = mid + 1
340- }
341- }
342- return ceiling
325+ private fun MediaConfig.roundedUpToDecodeBucket (
326+ dimension : Int ,
327+ ): Int {
328+ val currentBuckets = imageSizeBuckets()
329+ val search = currentBuckets.binarySearch(dimension)
330+ val index = if (search >= 0 ) search else search.inv ()
331+ return currentBuckets[
332+ minOf(index, currentBuckets.size - 1 ),
333+ ]
343334}
344335
345- private val DecodeBucketsPx = intArrayOf(
346- 96 , 128 , 256 , 320 , 480 , 640 ,
347- 720 , 1024 , 1280 , 1536 , 1920 ,
348- )
349-
350336private val ImageInterpolationSpec = spring<Float >(
351337 stiffness = Spring .StiffnessLow ,
352338)
0 commit comments