Skip to content

Commit

Permalink
improved performance
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed Apr 30, 2024
1 parent 38a55d2 commit dd1adad
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 96 deletions.
1 change: 1 addition & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ kotlin {
implementation(libs.kasechange)

implementation(libs.oidc)
implementation(libs.kache)

implementation("dev.datlag.sheets-compose-dialogs:rating:2.0.0-SNAPSHOT")
implementation("dev.datlag.sheets-compose-dialogs:option:2.0.0-SNAPSHOT")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package dev.datlag.aniflow.common

import androidx.compose.runtime.*
import androidx.compose.ui.graphics.painter.Painter
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.kmpalette.DominantColorState
import dev.datlag.aniflow.LocalDI
import dev.datlag.aniflow.ui.navigation.Component
import dev.datlag.aniflow.ui.theme.SchemeTheme
Expand Down Expand Up @@ -33,14 +35,14 @@ fun Component.onRender(content: @Composable () -> Unit) {
}

@Composable
fun Component.onRenderWithScheme(key: Any?, content: @Composable () -> Unit) {
fun Component.onRenderWithScheme(key: Any?, content: @Composable (DominantColorState<Painter>) -> Unit) {
onRender {
SchemeTheme(key, content)
}
}

@Composable
fun Component.onRenderApplyCommonScheme(key: Any?, content: @Composable () -> Unit) {
fun Component.onRenderApplyCommonScheme(key: Any?, content: @Composable (DominantColorState<Painter>) -> Unit) {
onRenderWithScheme(key, content)

SchemeTheme.setCommon(key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import dev.datlag.aniflow.anilist.model.Medium
import dev.datlag.aniflow.common.preferred
import dev.datlag.aniflow.settings.Settings
import dev.datlag.aniflow.settings.model.AppSettings
import dev.datlag.aniflow.ui.theme.LocalDominantColorState
import dev.datlag.aniflow.ui.theme.SchemeTheme
import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import org.kodein.di.instance
import org.kodein.di.instanceOrNull

Expand All @@ -34,6 +36,8 @@ fun AiringCard(
modifier: Modifier = Modifier,
onClick: (Medium) -> Unit
) {
val schemeState = LocalDominantColorState.current

airing.media?.let(::Medium)?.let { media ->
Card(
modifier = modifier,
Expand Down Expand Up @@ -61,27 +65,27 @@ fun AiringCard(
model = media.coverImage.medium,
contentScale = ContentScale.Crop,
onSuccess = { state ->
SchemeTheme.update(
key = media.id,
input = state.painter,
scope = scope
)
if (schemeState != null) {
scope.launch {
schemeState.updateFrom(state.painter)
}
}
}
),
onSuccess = { state ->
SchemeTheme.update(
key = media.id,
input = state.painter,
scope = scope
)
if (schemeState != null) {
scope.launch {
schemeState.updateFrom(state.painter)
}
}
}
),
onSuccess = { state ->
SchemeTheme.update(
key = media.id,
input = state.painter,
scope = scope
)
if (schemeState != null) {
scope.launch {
schemeState.updateFrom(state.painter)
}
}
}
)
Column(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import dev.datlag.aniflow.ui.theme.rememberSchemeThemeDominantColorState
import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch

@OptIn(ExperimentalStdlibApi::class)
@Composable
Expand All @@ -42,7 +43,7 @@ fun MediumCard(
) {
SchemeTheme(
key = medium.id
) {
) { schemeState ->
Card(
modifier = modifier,
onClick = {
Expand All @@ -63,9 +64,7 @@ fun MediumCard(
defaultColor = color ?: MaterialTheme.colorScheme.primary,
defaultOnColor = contentColorFor(color ?: MaterialTheme.colorScheme.primary)
)
var successPainter by remember { mutableStateOf<Painter?>(null) }

SchemeTheme.update(medium.id, successPainter)
val scope = rememberCoroutineScope()

AsyncImage(
model = medium.coverImage.extraLarge,
Expand All @@ -79,18 +78,28 @@ fun MediumCard(
model = medium.coverImage.medium,
contentScale = ContentScale.Crop,
onSuccess = { state ->
successPainter = state.painter
scope.launch {
schemeState.updateFrom(state.painter)
}
},
onError = {
successPainter = color?.let(::ColorPainter)
color?.let(::ColorPainter)?.let { painter ->
scope.launch {
schemeState.updateFrom(painter)
}
}
}
),
onSuccess = { state ->
successPainter = state.painter
scope.launch {
schemeState.updateFrom(state.painter)
}
}
),
onSuccess = { state ->
successPainter = state.painter
scope.launch {
schemeState.updateFrom(state.painter)
}
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import com.kmpalette.rememberPainterDominantColorState
import com.materialkolor.AnimatedDynamicMaterialTheme
import com.materialkolor.DynamicMaterialTheme
import com.materialkolor.ktx.isDisliked
import com.mayakapps.kache.InMemoryKache
import com.mayakapps.kache.KacheStrategy
import dev.datlag.aniflow.LocalDarkMode
import dev.datlag.tooling.async.scopeCatching
import dev.datlag.tooling.async.suspendCatching
import dev.datlag.tooling.compose.ioDispatcher
import dev.datlag.tooling.compose.launchIO
import dev.datlag.tooling.compose.withIOContext
Expand All @@ -29,79 +33,40 @@ import kotlin.coroutines.CoroutineContext
data object SchemeTheme {

internal val commonSchemeKey = MutableStateFlow<Any?>(null)
internal val colorState = MutableStateFlow<Map<Any, DominantColorState<Painter>>>(emptyMap())
internal val itemScheme = MutableStateFlow<Map<Any, Color?>>(emptyMap())
internal val kache = InMemoryKache<Any, DominantColorState<Painter>>(
maxSize = 25L * 1024 * 1024
) {
strategy = KacheStrategy.LRU
}

fun setCommon(key: Any?) {
commonSchemeKey.update { key }
}

@Composable
fun update(key: Any?, input: Painter?) {
if (input == null) {
return
}

LaunchedEffect(key, input) {
suspendUpdate(key, input)
}
}

fun update(key: Any?, input: Painter?, scope: CoroutineScope) {
scope.launchIO {
suspendUpdate(key, input)
}
}

fun update(key: Any?, color: Color?, scope: CoroutineScope) {
scope.launchIO {
suspendUpdate(key, color)
}
}

suspend fun suspendUpdate(key: Any?, input: Painter?): Boolean {
if (key == null || input == null) {
return false
}

withIOContext {
val useState = (colorState.firstOrNull() ?: colorState.value)[key]
useState?.updateFrom(input)

itemScheme.getAndUpdate {
it.toMutableMap().apply {
put(key, useState?.color)
}.toMap()
}
}
return true
}

suspend fun suspendUpdate(key: Any?, color: Color?) = suspendUpdate(key, color?.let { ColorPainter(it) })
}

@Composable
fun rememberSchemeThemeDominantColor(
key: Any?
key: Any?,
state: DominantColorState<Painter>? = null,
): Color? {
if (key == null) {
return null
}

val state = SchemeTheme.colorState.value[key] ?: rememberPainterDominantColorState(
val fallbackState = remember(state) {
state
} ?: remember(key) {
SchemeTheme.kache.getIfAvailable(key)
} ?: rememberPainterDominantColorState(
coroutineContext = ioDispatcher()
)
SchemeTheme.colorState.update {
it.toMutableMap().apply {
put(key, state)
}.toMap()
val useState by produceState(fallbackState, key) {
value = withIOContext {
SchemeTheme.kache.getOrPut(key) { fallbackState }
} ?: fallbackState
}

val color by remember(key) {
SchemeTheme.itemScheme.map { it[key] }
}.collectAsStateWithLifecycle(SchemeTheme.itemScheme.value[key])

return color
return remember(useState) { useState.color }
}

@Composable
Expand All @@ -113,25 +78,25 @@ fun rememberSchemeThemeDominantColorState(
isSwatchValid: (Palette.Swatch) -> Boolean = { true },
builder: Palette.Builder.() -> Unit = {},
): DominantColorState<Painter> {
val state by remember(key) {
SchemeTheme.colorState.map { it[key] }
}.collectAsStateWithLifecycle(SchemeTheme.colorState.value[key])

return state ?: rememberPainterDominantColorState(
val fallbackState = remember(key) {
key?.let { SchemeTheme.kache.getIfAvailable(it) }
} ?: rememberPainterDominantColorState(
defaultColor = defaultColor,
defaultOnColor = defaultOnColor,
coroutineContext = coroutineContext,
builder = builder,
isSwatchValid = isSwatchValid
).also {
if (key != null) {
SchemeTheme.colorState.update { map ->
map.toMutableMap().apply {
put(key, it)
}
}
)
val state by produceState(fallbackState, key) {
value = withIOContext {
key?.let {
SchemeTheme.kache.getOrPut(it) { fallbackState }
} ?: fallbackState
}
}

return state
}

@Composable
Expand Down Expand Up @@ -166,19 +131,27 @@ fun rememberSchemeThemeDominantColorState(
)
}

val LocalDominantColorState = compositionLocalOf<DominantColorState<Painter>?>{ null }

@Composable
fun SchemeTheme(key: Any?, content: @Composable () -> Unit) {
fun SchemeTheme(key: Any?, content: @Composable (DominantColorState<Painter>) -> Unit) {
val state = rememberSchemeThemeDominantColorState(key)

DynamicMaterialTheme(
seedColor = rememberSchemeThemeDominantColor(key) ?: MaterialTheme.colorScheme.primary,
seedColor = rememberSchemeThemeDominantColor(key, state) ?: MaterialTheme.colorScheme.primary,
useDarkTheme = LocalDarkMode.current,
animate = true
) {
content()
CompositionLocalProvider(
LocalDominantColorState provides state,
) {
content(state)
}
}
}

@Composable
fun CommonSchemeTheme(content: @Composable () -> Unit) {
fun CommonSchemeTheme(content: @Composable (DominantColorState<Painter>) -> Unit) {
val key by SchemeTheme.commonSchemeKey.collectAsStateWithLifecycle()

SchemeTheme(key, content)
Expand Down

0 comments on commit dd1adad

Please sign in to comment.