Search code examples
androidkotlinandroid-drawableandroid-theme

How to get color based on attribute name and current theme?


In the Markwon library, I want to add a plugin that will show an image from the application resources if write ![alt text](drawable://icon_name) in markdown format.

The code below works. But tintColor is not applied to the image. Because of this, it is not visible in the dark theme. Therefore, I added the value of the android.R.attr.colorControlNormal resource to change the tintColor of the drawable. Apparently Markwon erases this value (it is in the markup of the drawable file in the format "?attr/colorControlNormal".

It worked. But the color does not change when switching to a dark theme. The value of the android.R.attr.colorControlNormal attribute remains the same as with the white theme. What could be the mistake?

When this drawable is used directly in markup on the toolbar or other places, the tintColor is changed according to the theme.

Update 1

The issue with context. When an instance of markwon provided by Hilt with @ApplicationContext, the tintColor is incorrect. If create the instance of markwon locally in a fragment, the tintColor will be correct.

val tintColor = MaterialColors.getColor(context, android.R.attr.colorControlNormal, Color.RED)

enter image description here

enter image description here

// https://noties.io/Markwon/docs/v4/image/#custom-schemehandler
class DrawableImagesConfigure(private val context: Context) : ImagesPlugin.ImagesConfigure {

    override fun configureImages(plugin: ImagesPlugin) {
        plugin.addSchemeHandler(schemeHandler)
    }

    private val schemeHandler = object : SchemeHandler() {
        @SuppressLint("DiscouragedApi")
        // will handle URLs like `drawable://ic_account_24dp_white`
        override fun handle(raw: String, uri: Uri): ImageItem {
            val drawableName = raw.substring("drawable://".length)
            val drawableId = context.resources.getIdentifier(drawableName, "drawable", context.packageName)

            // it's fine if it throws, async-image-loader will catch exception
            val drawable: Drawable = AppCompatResources.getDrawable(context, drawableId)
                ?: throw Exception("The drawable with drawableId $drawableId does not exist")

            // every time the same value
            val tintColor = MaterialColors.getColor(context, android.R.attr.colorControlNormal, Color.RED)
            drawable.setTint(tintColor)
            return ImageItem.withResult(drawable)
        }

        override fun supportedSchemes(): Collection<String> {
            return setOf("drawable")
        }
    }
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:tint="?attr/colorControlNormal"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M4,15h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM4,19h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,17c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM4,11h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM3,6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,5c-0.55,0 -1,0.45 -1,1z" />
</vector>

Solution

  • // Fragment
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        DrawableImagesConfigure.updateTintColor(requireContext())
    }
    
    class DrawableImagesConfigure(@ApplicationContext private val context: Context) : ImagesPlugin.ImagesConfigure {
    
        private val schemeHandler = object : SchemeHandler() {
            override fun handle(raw: String, uri: Uri): ImageItem {
                // ...
                DrawableCompat.setTint(drawable, tintColorState.value)
                // ...
            }
    
            // ...
        }
    
        companion object {
            private val tintColorState = MutableStateFlow(Color.RED)
    
            fun updateTintColor(context: Context) {
                tintColorState.value = MaterialColors.getColor(context, android.R.attr.colorControlNormal, Color.RED)
            }
        }
    }