Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support placeholder with painter #5225

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
Expand Down Expand Up @@ -101,8 +101,6 @@ public fun GlideImage(
val requestManager: RequestManager = LocalContext.current.let { remember(it) { Glide.with(it) } }
val requestBuilder =
rememberRequestBuilderWithDefaults(model, requestManager, requestBuilderTransform, contentScale)
.let { loading?.apply(it::placeholder, it::placeholder) ?: it }
.let { failure?.apply(it::error, it::error) ?: it }

val overrideSize: Size? = requestBuilder.overrideSize()
val size = rememberResolvableSize(overrideSize)
Expand All @@ -124,8 +122,8 @@ public fun GlideImage(
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
placeholder = loading?.maybeComposable(),
failure = failure?.maybeComposable(),
placeholder = loading,
failure = failure,
)
}

Expand All @@ -136,20 +134,36 @@ private fun PreviewResourceOrDrawable(
contentDescription: String?,
modifier: Modifier,
) {
val drawable =
val painter =
when(loading) {
is Placeholder.OfDrawable -> loading.drawable
is Placeholder.OfResourceId -> LocalContext.current.getDrawable(loading.resourceId)
is Placeholder.OfDrawable -> rememberDrawablePainter(loading.drawable)
is Placeholder.OfResourceId -> rememberDrawablePainter(LocalContext.current.getDrawable(loading.resourceId))
is Placeholder.OfPainter -> loading.painter
is Placeholder.OfComposable ->
throw IllegalArgumentException("Composables should go through the production codepath")
}
Image(
painter = rememberDrawablePainter(drawable),
painter = painter,
modifier = modifier,
contentDescription = contentDescription,
)
}

@OptIn(ExperimentalGlideComposeApi::class)
@Composable
private fun getPlaceholderPainter(placeholder: Placeholder?): Painter? {
if (placeholder == null) return null
val painter =
when(placeholder) {
is Placeholder.OfDrawable -> placeholder.drawable?.toPainter()
is Placeholder.OfResourceId -> LocalContext.current.getDrawable(placeholder.resourceId)?.toPainter()
is Placeholder.OfPainter -> placeholder.painter
is Placeholder.OfComposable ->
throw IllegalArgumentException("Composables should go through the production codepath")
}
return painter
}

/**
* Used to specify a [Drawable] to use in conjunction with [GlideImage]'s `loading` or `failure`
* parameters.
Expand All @@ -173,6 +187,16 @@ public fun placeholder(drawable: Drawable?): Placeholder = Placeholder.OfDrawabl
public fun placeholder(@DrawableRes resourceId: Int): Placeholder =
Placeholder.OfResourceId(resourceId)

/**
* Used to specify a [Painter] to use in conjunction with [GlideImage]'s `loading` or `failure`
* parameters.
*
* Ideally [painter] is non-null.
*/
@ExperimentalGlideComposeApi
public fun placeholder(painter: Painter): Placeholder =
Placeholder.OfPainter(painter)

/**
* Used to specify a [Composable] function to use in conjunction with [GlideImage]'s `loading` or
* `failure` parameter.
Expand All @@ -184,6 +208,8 @@ public fun placeholder(@DrawableRes resourceId: Int): Placeholder =
public fun placeholder(composable: @Composable () -> Unit): Placeholder =
Placeholder.OfComposable(composable)



/**
* Content to display during a particular state of a Glide Request, for example while the request is
* loading or if the request fails.
Expand All @@ -201,11 +227,13 @@ public sealed class Placeholder {
internal class OfDrawable(internal val drawable: Drawable?) : Placeholder()
internal class OfResourceId(@DrawableRes internal val resourceId: Int) : Placeholder()
internal class OfComposable(internal val composable: @Composable () -> Unit) : Placeholder()
internal class OfPainter(internal val painter: Painter) : Placeholder()

internal fun isResourceOrDrawable() =
when (this) {
is OfDrawable -> true
is OfResourceId -> true
is OfPainter -> true
is OfComposable -> false
}

Expand Down Expand Up @@ -269,7 +297,7 @@ private fun RequestBuilder<Drawable>.contentScaleTransform(
// TODO(judds): Think about how to handle the various fills
}

@OptIn(InternalGlideApi::class, ExperimentGlideFlows::class)
@OptIn(InternalGlideApi::class, ExperimentGlideFlows::class, ExperimentalGlideComposeApi::class)
@Composable
private fun SizedGlideImage(
requestBuilder: RequestBuilder<Drawable>,
Expand All @@ -280,8 +308,8 @@ private fun SizedGlideImage(
contentScale: ContentScale,
alpha: Float,
colorFilter: ColorFilter?,
placeholder: @Composable (() -> Unit)?,
failure: @Composable (() -> Unit)?,
placeholder: Placeholder?,
failure: Placeholder?,
) {
// Use a Box so we can infer the size if the request doesn't have an explicit size.
@Composable fun @Composable () -> Unit.boxed() = Box(modifier = modifier) { this@boxed() }
Expand All @@ -290,11 +318,13 @@ private fun SizedGlideImage(
rememberGlidePainter(
requestBuilder = requestBuilder,
size = size,
getPlaceholderPainter(placeholder),
getPlaceholderPainter(failure),
)
if (placeholder != null && painter.status.showPlaceholder()) {
placeholder.boxed()
} else if (failure != null && painter.status == Status.FAILED) {
failure.boxed()
if (placeholder != null && !placeholder.isResourceOrDrawable() && painter.status.showPlaceholder()) {
placeholder.maybeComposable()?.boxed()
} else if (failure != null && !failure.isResourceOrDrawable() && painter.status == Status.FAILED) {
failure.maybeComposable()?.boxed()
} else {
Image(
painter = painter,
Expand All @@ -303,7 +333,7 @@ private fun SizedGlideImage(
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
modifier = modifier.then(Modifier.semantics { displayedDrawable = painter.currentDrawable })
modifier = modifier
)
}
}
Expand All @@ -321,12 +351,14 @@ private fun Status.showPlaceholder(): Boolean =
private fun rememberGlidePainter(
requestBuilder: RequestBuilder<Drawable>,
size: ResolvableGlideSize,
placeholder: Painter?,
failure: Painter?,
): GlidePainter {
val scope = rememberCoroutineScope()
// TODO(judds): Calling onRemembered here manually might make a minor improvement in how quickly
// the image load is started, but it also triggers a recomposition. I can't figure out why it
// triggers a recomposition
return remember(requestBuilder, size) { GlidePainter(requestBuilder, size, scope) }
return remember(requestBuilder, size) { GlidePainter(requestBuilder, size, placeholder, failure, scope) }
}

internal val DisplayedDrawableKey =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ internal class GlidePainter
constructor(
private val requestBuilder: RequestBuilder<Drawable>,
private val resolvableSize: ResolvableGlideSize,
private val placeholder: Painter?,
private val failure: Painter?,
scope: CoroutineScope,
) : Painter(), RememberObserver {
@OptIn(ExperimentGlideFlows::class) internal var status: Status by mutableStateOf(Status.CLEARED)
internal val currentDrawable: MutableState<Drawable?> = mutableStateOf(null)
private var alpha: Float by mutableStateOf(DefaultAlpha)
private var colorFilter: ColorFilter? by mutableStateOf(null)
private var delegate: Painter? by mutableStateOf(null)
Expand Down Expand Up @@ -94,28 +95,30 @@ constructor(
requestBuilder.flowResolvable(resolvableSize).collect {
updateDelegate(
when (it) {
is Resource -> it.resource
is Placeholder -> it.placeholder
is Resource -> it.resource.toPainter()
is Placeholder -> getPlaceholderPainter(it)
}
)
status = it.status
}
}

private fun Drawable.toPainter() =
when (this) {
is BitmapDrawable -> BitmapPainter(bitmap.asImageBitmap())
is ColorDrawable -> ColorPainter(Color(color))
else -> DrawablePainter(mutate())
@OptIn(ExperimentGlideFlows::class)
private fun getPlaceholderPainter(placeholder: Placeholder<Drawable>): Painter? {
if(placeholder.status == Status.RUNNING || placeholder.status == Status.CLEARED) {
return this.placeholder
} else if (placeholder.status == Status.FAILED) {
return this.failure
}
return null
}

private fun updateDelegate(drawable: Drawable?) {
val newDelegate = drawable?.toPainter()
private fun updateDelegate(newDelegate: Painter?) {
// val newDelegate = drawable?.toPainter()
val oldDelegate = delegate
if (newDelegate !== oldDelegate) {
(oldDelegate as? RememberObserver)?.onForgotten()
(newDelegate as? RememberObserver)?.onRemembered()
currentDrawable.value = drawable
delegate = newDelegate
}
}
Expand All @@ -130,3 +133,4 @@ constructor(
return true
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.bumptech.glide.integration.compose

import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.ColorPainter
import com.google.accompanist.drawablepainter.DrawablePainter

fun Drawable.toPainter() =
when (this) {
is BitmapDrawable -> BitmapPainter(bitmap.asImageBitmap())
is ColorDrawable -> ColorPainter(Color(color))
else -> DrawablePainter(mutate())
}
Loading