Skip to content
Open
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 @@ -13,6 +13,7 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.drawable.toDrawable
import com.swmansion.enriched.R
import java.net.URL
Expand All @@ -21,11 +22,14 @@ import java.util.concurrent.Executors

class AsyncDrawable(
private val url: String,
private var placeholderTintColor: Int,
) : Drawable() {
private var internalDrawable: Drawable = Color.TRANSPARENT.toDrawable()
private val mainHandler = Handler(Looper.getMainLooper())
private val executor = Executors.newSingleThreadExecutor()
var isLoaded = false
var isShowingPlaceholder = false
private set

init {
internalDrawable.bounds = bounds
Expand Down Expand Up @@ -53,7 +57,9 @@ class AsyncDrawable(
} catch (e: Exception) {
Log.e("AsyncDrawable", "Failed to load: $url", e)

loadPlaceholderImage()
mainHandler.post {
loadPlaceholderImage()
}
Comment on lines +60 to +62

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was caught by Copilot. Previously loadPlaceholderImage which mutates AsyncDrawable internal state, could be run from a secondary thread. This may lead to some state desynchronization, so we delegate it to the main thread, just like we already do few lines above

} finally {
isLoaded = true
onLoaded?.invoke()
Expand Down Expand Up @@ -94,7 +100,20 @@ class AsyncDrawable(
}

private fun loadPlaceholderImage() {
internalDrawable = ResourceManager.getDrawableResource(R.drawable.broken_image)
val drawable = ResourceManager.getDrawableResource(R.drawable.broken_image)

DrawableCompat.setTint(drawable, placeholderTintColor)

Comment thread
hejsztynx marked this conversation as resolved.
isShowingPlaceholder = true
internalDrawable = drawable
}

fun applyPlaceholderTint(color: Int) {
placeholderTintColor = color

if (!isShowingPlaceholder) return

DrawableCompat.setTint(internalDrawable, color)
}

override fun draw(canvas: Canvas) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.swmansion.enriched.common.spans.EnrichedUnderlineSpan
import com.swmansion.enriched.common.spans.EnrichedUnorderedListSpan

interface EnrichedSpanFactory<T> {
var textColor: Int

fun createAlignmentSpan(cssValue: String): EnrichedAlignmentSpan

fun createBoldSpan(style: T): EnrichedBoldSpan
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.os.Looper
import android.text.Spannable
import android.text.style.ImageSpan
import android.util.Log
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.withSave
import com.swmansion.enriched.common.AsyncDrawable
Expand All @@ -26,9 +27,28 @@ open class EnrichedImageSpan :
private var width: Int = 0
private var height: Int = 0

constructor(drawable: Drawable, source: String, width: Int, height: Int) : super(drawable, source, ALIGN_BASELINE) {
private var isStaticPlaceholder: Boolean = false

constructor(
drawable: Drawable,
source: String,
width: Int,
height: Int,
isStaticPlaceholder: Boolean = false,
) : super(drawable, source, ALIGN_BASELINE) {
this.width = width
this.height = height
this.isStaticPlaceholder = isStaticPlaceholder
}

fun refreshPlaceholderTint(color: Int) {
val d = drawable

if (d is AsyncDrawable) {
d.applyPlaceholderTint(color)
} else if (isStaticPlaceholder) {
DrawableCompat.setTint(d, color)
}
}

override fun draw(
Expand Down Expand Up @@ -134,11 +154,12 @@ open class EnrichedImageSpan :
src: String,
width: Int,
height: Int,
placeholderTintColor: Int,
): Drawable? {
var cleanPath = src

if (cleanPath.startsWith("http://") || cleanPath.startsWith("https://")) {
return AsyncDrawable(cleanPath)
return AsyncDrawable(cleanPath, placeholderTintColor)
}

if (cleanPath.startsWith("file://")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.swmansion.enriched.text

import android.graphics.Color
import com.swmansion.enriched.common.parser.EnrichedSpanFactory
import com.swmansion.enriched.text.spans.EnrichedTextAlignmentSpan
import com.swmansion.enriched.text.spans.EnrichedTextBlockQuoteSpan
Expand All @@ -23,6 +24,8 @@ import com.swmansion.enriched.text.spans.EnrichedTextUnderlineSpan
import com.swmansion.enriched.text.spans.EnrichedTextUnorderedListSpan

class EnrichedTextSpanFactory : EnrichedSpanFactory<EnrichedTextStyle> {
override var textColor: Int = Color.BLACK

override fun createAlignmentSpan(cssValue: String) = EnrichedTextAlignmentSpan(cssValue)

override fun createBoldSpan(style: EnrichedTextStyle) = EnrichedTextBoldSpan(style)
Expand Down Expand Up @@ -52,7 +55,7 @@ class EnrichedTextSpanFactory : EnrichedSpanFactory<EnrichedTextStyle> {
source: String,
width: Int,
height: Int,
) = EnrichedTextImageSpan.createEnrichedImageSpan(source, width, height)
) = EnrichedTextImageSpan.createEnrichedImageSpan(source, width, height, textColor)

override fun createH1Span(style: EnrichedTextStyle) = EnrichedTextH1Span(style)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,20 @@ class EnrichedTextView : AppCompatTextView {
}

fun setColor(colorInt: Int?) {
if (colorInt == null) {
setTextColor(Color.BLACK)
return
}
val resolvedColor = colorInt ?: Color.BLACK

setTextColor(resolvedColor)
spannableFactory.textColor = resolvedColor
refreshImagePlaceholderTints(resolvedColor)
}

setTextColor(colorInt)
private fun refreshImagePlaceholderTints(color: Int) {
val spanned = text as? Spanned ?: return
val spans = spanned.getSpans(0, spanned.length, EnrichedTextImageSpan::class.java)

for (span in spans) {
span.refreshPlaceholderTint(color)
}
}

fun setFontSize(size: Float) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.swmansion.enriched.text.spans
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import androidx.core.graphics.drawable.DrawableCompat
import com.swmansion.enriched.R
import com.swmansion.enriched.common.AsyncDrawable
import com.swmansion.enriched.common.ResourceManager
Expand All @@ -15,7 +16,8 @@ class EnrichedTextImageSpan(
source: String,
width: Int,
height: Int,
) : EnrichedImageSpan(drawable, source, width, height),
isStaticPlaceholder: Boolean = false,
) : EnrichedImageSpan(drawable, source, width, height, isStaticPlaceholder),
EnrichedTextSpan {
override val dependsOnHtmlStyle = false

Expand Down Expand Up @@ -44,14 +46,18 @@ class EnrichedTextImageSpan(
src: String,
width: Int,
height: Int,
placeholderTintColor: Int,
): EnrichedImageSpan {
var imgDrawable = prepareDrawableForImage(src, width, height)
var imgDrawable = prepareDrawableForImage(src, width, height, placeholderTintColor)
var isStaticPlaceholder = false

if (imgDrawable == null) {
imgDrawable = ResourceManager.getDrawableResource(R.drawable.broken_image)
isStaticPlaceholder = true
DrawableCompat.setTint(imgDrawable, placeholderTintColor)
}

return EnrichedTextImageSpan(imgDrawable, src, width, height)
return EnrichedTextImageSpan(imgDrawable, src, width, height, isStaticPlaceholder)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.swmansion.enriched.textinput

import android.graphics.Color
import com.swmansion.enriched.common.parser.EnrichedSpanFactory
import com.swmansion.enriched.common.spans.EnrichedImageSpan
import com.swmansion.enriched.textinput.spans.EnrichedInputAlignmentSpan
Expand All @@ -25,6 +26,8 @@ import com.swmansion.enriched.textinput.spans.EnrichedInputUnorderedListSpan
import com.swmansion.enriched.textinput.styles.HtmlStyle

class EnrichedTextInputSpannableFactory : EnrichedSpanFactory<HtmlStyle> {
override var textColor: Int = Color.BLACK

override fun createAlignmentSpan(cssValue: String) = EnrichedInputAlignmentSpan(cssValue)

override fun createBoldSpan(style: HtmlStyle) = EnrichedInputBoldSpan(style)
Expand Down Expand Up @@ -54,7 +57,7 @@ class EnrichedTextInputSpannableFactory : EnrichedSpanFactory<HtmlStyle> {
source: String,
width: Int,
height: Int,
): EnrichedImageSpan = EnrichedInputImageSpan.createEnrichedImageSpan(source, width, height)
): EnrichedImageSpan = EnrichedInputImageSpan.createEnrichedImageSpan(source, width, height, textColor)

override fun createH1Span(style: HtmlStyle) = EnrichedInputH1Span(style)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,12 +545,20 @@ class EnrichedTextInputView :
}

fun setColor(colorInt: Int?) {
if (colorInt == null) {
setTextColor(Color.BLACK)
return
}
val resolvedColor = colorInt ?: Color.BLACK

setTextColor(colorInt)
setTextColor(resolvedColor)
spannableFactory.textColor = resolvedColor
refreshImagePlaceholderTints(resolvedColor)
}

private fun refreshImagePlaceholderTints(color: Int) {
val liveText = text ?: return
val spans = liveText.getSpans(0, liveText.length, EnrichedInputImageSpan::class.java)

for (span in spans) {
span.refreshPlaceholderTint(color)
}
}

fun setFontSize(size: Float) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.swmansion.enriched.textinput.spans

import android.graphics.drawable.Drawable
import androidx.core.graphics.drawable.DrawableCompat
import com.swmansion.enriched.R
import com.swmansion.enriched.common.ResourceManager
import com.swmansion.enriched.common.spans.EnrichedImageSpan
Expand All @@ -12,7 +13,8 @@ class EnrichedInputImageSpan(
source: String,
width: Int,
height: Int,
) : EnrichedImageSpan(drawable, source, width, height),
isStaticPlaceholder: Boolean = false,
) : EnrichedImageSpan(drawable, source, width, height, isStaticPlaceholder),
EnrichedInputSpan {
override val dependsOnHtmlStyle: Boolean = false

Expand All @@ -23,14 +25,18 @@ class EnrichedInputImageSpan(
src: String,
width: Int,
height: Int,
placeholderTintColor: Int,
): EnrichedInputImageSpan {
var imgDrawable = prepareDrawableForImage(src, width, height)
var imgDrawable = prepareDrawableForImage(src, width, height, placeholderTintColor)
var isStaticPlaceholder = false

if (imgDrawable == null) {
imgDrawable = ResourceManager.getDrawableResource(R.drawable.broken_image)
isStaticPlaceholder = true
DrawableCompat.setTint(imgDrawable, placeholderTintColor)
}

return EnrichedInputImageSpan(imgDrawable, src, width, height)
return EnrichedInputImageSpan(imgDrawable, src, width, height, isStaticPlaceholder)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ class ParametrizedStyles(
}

val (imageStart, imageEnd) = spannable.getSafeSpanBoundaries(start, start + 1)
val span = EnrichedInputImageSpan.createEnrichedImageSpan(src, width.toInt(), height.toInt())
val span = EnrichedInputImageSpan.createEnrichedImageSpan(src, width.toInt(), height.toInt(), view.currentTextColor)
span.observeAsyncDrawableLoaded(view.text)

spannable.setSpan(span, imageStart, imageEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
Expand Down
5 changes: 4 additions & 1 deletion ios/utils/AttachmentLayoutUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ + (void)handleAttachmentUpdate:(MediaAttachment *)attachment
imgView = [[UIImageView alloc] initWithFrame:rect];
imgView.contentMode =
UIViewContentModeScaleAspectFit;
imgView.tintColor = [UIColor labelColor];

// Add it directly to the TextView
[textView addSubview:imgView];
Expand All @@ -76,6 +75,10 @@ + (void)handleAttachmentUpdate:(MediaAttachment *)attachment
if (!CGRectEqualToRect(imgView.frame, rect)) {
imgView.frame = rect;
}

// Keep the placeholder tint in sync with the
// current font color
imgView.tintColor = [config primaryColor];
UIImage *targetImage =
attachment.storedAnimatedImage ?: attachment.image;

Expand Down
Loading