jordond / MaterialKolor

🎨 A Compose multiplatform library for generating dynamic Material3 color schemes from a seed color
https://materialkolor.com
MIT License
525 stars 10 forks source link

Asynchronous theme switching #216

Open sosauce opened 1 month ago

sosauce commented 1 month ago

Hello ! I am building a music app, and thought it would be cool to use your library to change the app's WHOLE (this might be important) theme based on the currently playing music's art. I've noticed that the app freezes for ~3 seconds (this probably will vary depending on the device), I also have to have my custom way of getting the imageBitmap from the currently playing musics art (code below) which even if run on the IO thread, still freezes the app. I also saw that the Log "content" gets logged 3 times when switching theme, if it helps.

Here is my question : If not already, is there a way to implement asynchronous theme switching ?

I think that it's due to the fact that's it's my whole app that is switching theme, but a smooth switching would be nice !

Full code :

@Composable
fun ArtTheme(content: @Composable () -> Unit) {

    val seedColor = getImageBitmap()?.let {
        rememberThemeColor(
        image = it,
        fallback = MaterialTheme.colorScheme.background
        )
    }

    val state = rememberDynamicMaterialThemeState(
        seedColor = seedColor ?: MaterialTheme.colorScheme.background,
        isDark = true
    )

    DynamicMaterialTheme(
        animate = true,
        state = state
    ) {
        Log.d("content", "content wahoo")
        content()
    }

}

@Composable
fun getImageBitmap() : ImageBitmap? {
    val vm = koinViewModel<MusicViewModel>()
    var imageBitmap by remember { mutableStateOf<ImageBitmap?>(null) }
    val context = LocalContext.current
    LaunchedEffect(vm.currentArt) {
        withContext(Dispatchers.IO) {
            try {
                val image = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    context.contentResolver.loadThumbnail(
                        Uri.parse(vm.currentMusicUri) ?: Uri.EMPTY, Size(640, 480), null
                    )
                } else {
                    MediaStore.Images.Thumbnails.getThumbnail(
                        context.contentResolver,
                        1,
                        MediaStore.Images.Thumbnails.MINI_KIND,
                        null
                    )
                    // replace 1 with actual ID
                }
                imageBitmap = image.asImageBitmap()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
    return imageBitmap
}
jordond commented 1 month ago

The log in the content block will get called multiple times because the theme is animating from one to another.

As for the slowdown. Whole app theme changing isn't an issue usually. As I have used it several times with no slowdowns.

It's most likely the call to rememberThemeColor. It is not very fast and in a future version I plan on removing the current API for it and modifying it.

It should really be called in a coroutine on a different thread.

I will also be investigating ways to improve its performance. But the current implementation does come from the material-color-utilities.

sosauce commented 1 month ago

After some testing it appears the size of the image has an impact, I went from 640 x 480 to 50, 50 and it works perfectly fine now