@@ -65,7 +65,7 @@ import kotlin.math.roundToInt
65
65
66
66
@ExperimentalGlideComposeApi
67
67
internal interface RequestListener {
68
- fun onStateChanged (model : Any? , drawable : Drawable ? , requestState : RequestState )
68
+ fun onStateChanged (model : Any? , painter : Painter ? , requestState : RequestState )
69
69
}
70
70
71
71
@ExperimentalGlideComposeApi
@@ -79,6 +79,8 @@ internal fun Modifier.glideNode(
79
79
transitionFactory : Transition .Factory ? = null,
80
80
requestListener : RequestListener ? = null,
81
81
draw : Boolean? = null,
82
+ loadingPlaceholder : Painter ? = null,
83
+ errorPlaceholder : Painter ? = null,
82
84
): Modifier {
83
85
return this then GlideNodeElement (
84
86
requestBuilder,
@@ -89,6 +91,8 @@ internal fun Modifier.glideNode(
89
91
requestListener,
90
92
draw,
91
93
transitionFactory,
94
+ loadingPlaceholder,
95
+ errorPlaceholder,
92
96
)
93
97
.clipToBounds()
94
98
.semantics {
@@ -110,6 +114,8 @@ internal data class GlideNodeElement constructor(
110
114
private val requestListener : RequestListener ? ,
111
115
private val draw : Boolean? ,
112
116
private val transitionFactory : Transition .Factory ? ,
117
+ private val loadingPlaceholder : Painter ? ,
118
+ private val errorPlaceholder : Painter ? ,
113
119
) : ModifierNodeElement<GlideNode>() {
114
120
override fun create (): GlideNode {
115
121
val result = GlideNode ()
@@ -127,6 +133,8 @@ internal data class GlideNodeElement constructor(
127
133
requestListener,
128
134
draw,
129
135
transitionFactory,
136
+ loadingPlaceholder,
137
+ errorPlaceholder,
130
138
)
131
139
}
132
140
@@ -167,10 +175,12 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
167
175
private var requestListener: RequestListener ? = null
168
176
169
177
private var currentJob: Job ? = null
170
- private var painter: Painter ? = null
178
+ private var primary: Primary ? = null
179
+
180
+ private var loadingPlaceholder: Painter ? = null
181
+ private var errorPlaceholder: Painter ? = null
171
182
172
183
// Only used for debugging
173
- private var drawable: Drawable ? = null
174
184
private var state: RequestState = RequestState .Loading
175
185
private var placeholder: Painter ? = null
176
186
private var isFirstResource = true
@@ -213,11 +223,18 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
213
223
requestListener : RequestListener ? ,
214
224
draw : Boolean? ,
215
225
transitionFactory : Transition .Factory ? ,
226
+ loadingPlaceholder : Painter ? ,
227
+ errorPlaceholder : Painter ? ,
216
228
) {
217
229
// Other attributes can be refreshed by re-drawing rather than restarting a request
218
230
val restartLoad =
219
231
! this ::requestBuilder.isInitialized ||
220
232
requestBuilder != this .requestBuilder
233
+ // TODO(sam): Avoid restarting the entire load if we just change the placeholder. State
234
+ // management makes this complicated and this might not be a common use case, so we
235
+ // haven't yet done so.
236
+ || loadingPlaceholder != this .loadingPlaceholder
237
+ || errorPlaceholder != this .errorPlaceholder
221
238
222
239
this .requestBuilder = requestBuilder
223
240
this .contentScale = contentScale
@@ -227,14 +244,16 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
227
244
this .requestListener = requestListener
228
245
this .draw = draw ? : true
229
246
this .transitionFactory = transitionFactory ? : DoNotTransition .Factory
247
+ this .loadingPlaceholder = loadingPlaceholder
248
+ this .errorPlaceholder = errorPlaceholder
230
249
this .resolvableGlideSize =
231
250
requestBuilder.maybeImmediateSize()
232
251
? : inferredGlideSize?.let { ImmediateGlideSize (it) }
233
252
? : AsyncGlideSize ()
234
253
235
254
if (restartLoad) {
236
255
clear()
237
- updateDrawable (null )
256
+ updatePrimary (null )
238
257
239
258
// If we're not attached, we'll be measured when we eventually are attached.
240
259
if (isAttached) {
@@ -321,7 +340,7 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
321
340
}
322
341
}
323
342
324
- painter?.let { painter ->
343
+ primary?. painter?.let { painter ->
325
344
drawContext.canvas.withSave {
326
345
drawablePositionAndSize = drawOne(painter, drawablePositionAndSize) { size ->
327
346
transition.drawCurrent.invoke(this , painter, size, alpha, colorFilter)
@@ -347,7 +366,7 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
347
366
override fun onReset () {
348
367
super .onReset()
349
368
clear()
350
- updateDrawable (null )
369
+ updatePrimary (null )
351
370
}
352
371
353
372
@OptIn(ExperimentGlideFlows ::class )
@@ -388,27 +407,37 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
388
407
placeholderPositionAndSize = null
389
408
390
409
requestBuilder.flowResolvable(resolvableGlideSize).collect {
391
- val (state, drawable ) =
410
+ val (state, primary ) =
392
411
when (it) {
393
412
is Resource -> {
394
413
maybeAnimate(it)
395
- Pair (RequestState .Success (it.dataSource), it.resource)
414
+ Pair (RequestState .Success (it.dataSource), Primary . PrimaryDrawable ( it.resource) )
396
415
}
397
416
398
417
is Placeholder -> {
399
- val drawable = it.placeholder
400
418
val state = when (it.status) {
401
419
Status .RUNNING , Status .CLEARED -> RequestState .Loading
402
420
Status .FAILED -> RequestState .Failure
403
421
Status .SUCCEEDED -> throw IllegalStateException ()
404
422
}
405
- placeholder = drawable?.toPainter()
423
+ // Prefer the override Painters if set.
424
+ val painter = when (state) {
425
+ is RequestState .Loading -> loadingPlaceholder
426
+ is RequestState .Failure -> errorPlaceholder
427
+ is RequestState .Success -> throw IllegalStateException ()
428
+ }
429
+ val primary = if (painter != null ) {
430
+ Primary .PrimaryPainter (painter)
431
+ } else {
432
+ Primary .PrimaryDrawable (it.placeholder)
433
+ }
434
+ placeholder = primary.painter
406
435
placeholderPositionAndSize = null
407
- Pair (state, drawable )
436
+ Pair (state, primary )
408
437
}
409
438
}
410
- updateDrawable(drawable )
411
- requestListener?.onStateChanged(requestBuilder.internalModel, drawable , state)
439
+ updatePrimary(primary )
440
+ requestListener?.onStateChanged(requestBuilder.internalModel, primary.painter , state)
412
441
this @GlideNode.state = state
413
442
if (hasFixedSize) {
414
443
invalidateDraw()
@@ -419,17 +448,40 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
419
448
}
420
449
}
421
450
422
- private fun updateDrawable (drawable : Drawable ? ) {
423
- this .drawable = drawable
451
+ private sealed class Primary {
452
+ class PrimaryDrawable (override val drawable : Drawable ? ) : Primary() {
453
+ override val painter = drawable?.toPainter()
454
+ override fun onUnset () {
455
+ drawable?.callback = null
456
+ drawable?.setVisible(false , false )
457
+ (drawable as ? Animatable )?.stop()
458
+ }
459
+
460
+ override fun onSet (callback : Drawable .Callback ) {
461
+ drawable?.callback = callback
462
+ drawable?.setVisible(true , true )
463
+ (drawable as ? Animatable )?.start()
464
+ }
465
+ }
424
466
425
- this .drawable?.callback = null
426
- this .drawable?.setVisible(false , false )
427
- (this .drawable as ? Animatable )?.stop()
467
+ class PrimaryPainter (override val painter : Painter ? ) : Primary() {
468
+ override val drawable = null
469
+ override fun onUnset () {}
470
+ override fun onSet (callback : Drawable .Callback ) {}
471
+ }
428
472
429
- painter = drawable?.toPainter()
430
- drawable?.callback = callback
431
- drawable?.setVisible(true , true )
432
- (drawable as ? Animatable )?.start()
473
+ abstract val painter: Painter ?
474
+ abstract val drawable: Drawable ?
475
+
476
+ abstract fun onUnset ()
477
+
478
+ abstract fun onSet (callback : Drawable .Callback )
479
+ }
480
+
481
+ private fun updatePrimary (newPrimary : Primary ? ) {
482
+ this .primary?.onUnset()
483
+ this .primary = newPrimary
484
+ newPrimary?.onSet(callback)
433
485
drawablePositionAndSize = null
434
486
}
435
487
@@ -448,7 +500,7 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
448
500
currentJob?.cancel()
449
501
currentJob = null
450
502
state = RequestState .Loading
451
- updateDrawable (null )
503
+ updatePrimary (null )
452
504
}
453
505
454
506
@OptIn(InternalGlideApi ::class )
@@ -484,7 +536,7 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
484
536
)
485
537
}
486
538
487
- val intrinsicSize = painter?.intrinsicSize ? : return constraints
539
+ val intrinsicSize = primary?. painter?.intrinsicSize ? : return constraints
488
540
489
541
val intrinsicWidth =
490
542
if (constraints.hasFixedWidth) {
@@ -509,8 +561,8 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
509
561
510
562
val srcSize = Size (intrinsicWidth.toFloat(), intrinsicHeight.toFloat())
511
563
val scaleFactor = contentScale.computeScaleFactor(
512
- srcSize, Size (constrainedWidth.toFloat(), constrainedHeight.toFloat())
513
- )
564
+ srcSize, Size (constrainedWidth.toFloat(), constrainedHeight.toFloat())
565
+ )
514
566
if (scaleFactor == ScaleFactor .Unspecified ) {
515
567
return constraints
516
568
}
@@ -522,7 +574,8 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
522
574
}
523
575
524
576
override fun SemanticsPropertyReceiver.applySemantics () {
525
- displayedDrawable = { drawable }
577
+ displayedDrawable = { primary?.drawable }
578
+ displayedPainter = { primary?.painter }
526
579
}
527
580
528
581
override fun equals (other : Any? ): Boolean {
@@ -535,6 +588,8 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
535
588
&& draw == other.draw
536
589
&& transitionFactory == other.transitionFactory
537
590
&& alpha == other.alpha
591
+ && loadingPlaceholder == other.loadingPlaceholder
592
+ && errorPlaceholder == other.errorPlaceholder
538
593
}
539
594
return false
540
595
}
@@ -548,10 +603,16 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
548
603
result = 31 * result + requestListener.hashCode()
549
604
result = 31 * result + transitionFactory.hashCode()
550
605
result = 31 * result + alpha.hashCode()
606
+ result = 31 * result + loadingPlaceholder.hashCode()
607
+ result = 31 * result + errorPlaceholder.hashCode()
551
608
return result
552
609
}
553
610
}
554
611
555
612
internal val DisplayedDrawableKey =
556
613
SemanticsPropertyKey < () -> Drawable ? > (" DisplayedDrawable" )
557
614
internal var SemanticsPropertyReceiver .displayedDrawable by DisplayedDrawableKey
615
+
616
+ internal val DisplayedPainterKey =
617
+ SemanticsPropertyKey < () -> Painter ? > (" DisplayedPainter" )
618
+ internal var SemanticsPropertyReceiver .displayedPainter by DisplayedPainterKey
0 commit comments