Skip to content

Commit

Permalink
Move several internal common Compose functions into utils. Minor clea…
Browse files Browse the repository at this point in the history
…nup.
  • Loading branch information
colinrtwhite committed Jan 24, 2025
1 parent 0856145 commit 20de745
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,12 @@ package coil3.compose

import android.graphics.drawable.Drawable
import androidx.compose.ui.layout.ContentScale
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import coil3.request.lifecycle
import coil3.request.transitionFactory
import coil3.transition.CrossfadeTransition
import coil3.transition.TransitionTarget
import kotlin.time.Duration.Companion.milliseconds

internal actual fun validateRequestProperties(request: ImageRequest) {
require(request.target == null) { "request.target must be null." }
require(request.lifecycle == null) { "request.lifecycle must be null." }
}

internal actual fun maybeNewCrossfadePainter(
previous: AsyncImagePainter.State,
current: AsyncImagePainter.State,
Expand All @@ -29,7 +22,7 @@ internal actual fun maybeNewCrossfadePainter(

// Invoke the transition factory and wrap the painter in a `CrossfadePainter` if it returns
// a `CrossfadeTransformation`.
val transition = result.request.transitionFactory.create(fakeTransitionTarget, result)
val transition = result.request.transitionFactory.create(FakeTransitionTarget, result)
if (transition is CrossfadeTransition) {
return CrossfadePainter(
start = previous.painter.takeIf { previous is AsyncImagePainter.State.Loading },
Expand All @@ -44,7 +37,7 @@ internal actual fun maybeNewCrossfadePainter(
}
}

private val fakeTransitionTarget = object : TransitionTarget {
private val FakeTransitionTarget = object : TransitionTarget {
override val view get() = throw UnsupportedOperationException()
override val drawable: Drawable? get() = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package coil3.compose.internal

import coil3.request.ImageRequest
import coil3.request.lifecycle

internal actual fun validateRequestProperties(request: ImageRequest) {
require(request.target == null) { "request.target must be null." }
require(request.lifecycle == null) { "request.lifecycle must be null." }
}
47 changes: 20 additions & 27 deletions coil-compose-core/src/commonMain/kotlin/coil3/compose/AsyncImage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultFilterQ
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.MeasurePolicy
import coil3.ImageLoader
import coil3.compose.AsyncImagePainter.Companion.DefaultTransform
import coil3.compose.AsyncImagePainter.State
import coil3.compose.internal.AsyncImageState
import coil3.compose.internal.ContentPainterElement
import coil3.compose.internal.UseMinConstraintsMeasurePolicy
import coil3.compose.internal.onStateOf
import coil3.compose.internal.previewHandler
import coil3.compose.internal.requestOfWithSizeResolver
import coil3.compose.internal.transformOf
import coil3.compose.internal.validateRequest
import coil3.request.ImageRequest

/**
Expand Down Expand Up @@ -157,32 +159,23 @@ private fun AsyncImage(
validateRequest(request)

Layout(
modifier = modifier
.then(
ContentPainterElement(
request = request,
imageLoader = state.imageLoader,
modelEqualityDelegate = state.modelEqualityDelegate,
transform = transform,
onState = onState,
contentScale = contentScale,
filterQuality = filterQuality,
alignment = alignment,
alpha = alpha,
colorFilter = colorFilter,
clipToBounds = clipToBounds,
previewHandler = previewHandler(),
contentDescription = contentDescription,
),
modifier = modifier.then(
ContentPainterElement(
request = request,
imageLoader = state.imageLoader,
modelEqualityDelegate = state.modelEqualityDelegate,
transform = transform,
onState = onState,
contentScale = contentScale,
filterQuality = filterQuality,
alignment = alignment,
alpha = alpha,
colorFilter = colorFilter,
clipToBounds = clipToBounds,
previewHandler = previewHandler(),
contentDescription = contentDescription,
),
measurePolicy = UseMinConstraintsMeasurePolicy
),
measurePolicy = UseMinConstraintsMeasurePolicy,
)
}

// Saving it into a field allows us to
// - not allocate it again for each usage of AsyncImage
// - have the same object when the AsyncImage is reused, which allows us to skip unnecessary
// remeasure as the policy didn't change
internal val UseMinConstraintsMeasurePolicy = MeasurePolicy { _, constraints ->
layout(constraints.minWidth, constraints.minHeight) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package coil3.compose
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
Expand All @@ -15,13 +14,10 @@ import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultFilterQuality
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.util.trace
import coil3.Image
import coil3.ImageLoader
Expand All @@ -33,9 +29,11 @@ import coil3.compose.internal.AsyncImageState
import coil3.compose.internal.DeferredDispatchCoroutineScope
import coil3.compose.internal.launchUndispatched
import coil3.compose.internal.onStateOf
import coil3.compose.internal.previewHandler
import coil3.compose.internal.requestOf
import coil3.compose.internal.toScale
import coil3.compose.internal.transformOf
import coil3.compose.internal.validateRequest
import coil3.request.ErrorResult
import coil3.request.ImageRequest
import coil3.request.ImageResult
Expand Down Expand Up @@ -389,37 +387,6 @@ class AsyncImagePainter internal constructor(
}
}

@ReadOnlyComposable
@Composable
internal fun previewHandler(): AsyncImagePreviewHandler? {
return if (LocalInspectionMode.current) {
LocalAsyncImagePreviewHandler.current
} else {
null
}
}

internal fun validateRequest(request: ImageRequest) {
when (request.data) {
is ImageRequest.Builder -> unsupportedData(
name = "ImageRequest.Builder",
description = "Did you forget to call ImageRequest.Builder.build()?",
)
is ImageBitmap -> unsupportedData("ImageBitmap")
is ImageVector -> unsupportedData("ImageVector")
is Painter -> unsupportedData("Painter")
}
validateRequestProperties(request)
}

private fun unsupportedData(
name: String,
description: String = "If you wish to display this $name, use androidx.compose.foundation.Image.",
): Nothing = throw IllegalArgumentException("Unsupported type: $name. $description")

/** Validate platform-specific properties of an [ImageRequest]. */
internal expect fun validateRequestProperties(request: ImageRequest)

/** Create and return a [CrossfadePainter] if requested. */
internal expect fun maybeNewCrossfadePainter(
previous: State,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.unit.Constraints
import coil3.compose.internal.ZeroConstraints
import coil3.compose.internal.toSize
import coil3.size.Size
import coil3.size.SizeResolver
Expand All @@ -29,7 +30,7 @@ fun rememberConstraintsSizeResolver(): ConstraintsSizeResolver {
*/
@Stable
class ConstraintsSizeResolver : SizeResolver, LayoutModifier {
private var latestConstraints: Constraints = Constraints(maxWidth = 0, maxHeight = 0)
private var latestConstraints = ZeroConstraints
private var continuation: Continuation<Unit>? = null

override suspend fun size(): Size {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class CrossfadePainter(
var start: Painter? = start
private set

override val intrinsicSize get() = computeIntrinsicSize()
override val intrinsicSize: Size
get() = computeIntrinsicSize()

override fun DrawScope.onDraw() {
if (isDone) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import coil3.compose.AsyncImagePainter.Companion.DefaultTransform
import coil3.compose.AsyncImagePainter.State
import coil3.compose.internal.AsyncImageState
import coil3.compose.internal.SubcomposeContentPainterElement
import coil3.compose.internal.UseMinConstraintsMeasurePolicy
import coil3.compose.internal.onStateOf
import coil3.compose.internal.requestOfWithSizeResolver
import coil3.request.ImageRequest
Expand Down Expand Up @@ -275,18 +276,17 @@ fun SubcomposeAsyncImageScope.SubcomposeAsyncImageContent(
colorFilter: ColorFilter? = this.colorFilter,
clipToBounds: Boolean = this.clipToBounds,
) = Layout(
modifier = modifier
.then(
SubcomposeContentPainterElement(
painter = painter,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
clipToBounds = clipToBounds,
contentDescription = contentDescription,
),
modifier = modifier.then(
SubcomposeContentPainterElement(
painter = painter,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
clipToBounds = clipToBounds,
contentDescription = contentDescription,
),
),
measurePolicy = UseMinConstraintsMeasurePolicy,
)

Expand Down
Loading

0 comments on commit 20de745

Please sign in to comment.