Allow VectorPainter to be cached

This commit is contained in:
GG Bond authored and Vladimir Dostoyevskiy committed 2023-12-08 12:50:19 +08:00
1 parent 9a32b020cf
commit 0a2268d4de
2 files changed
+118 -95

No files matched your search

@@ -116,6 +116,7 @@ import com.hippo.ehviewer.ui.settings.showNewVersion
import com.hippo.ehviewer.ui.tools.IconFix
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.LocalTouchSlopProvider
import com.hippo.ehviewer.ui.tools.ProvideVectorPainterCache
import com.hippo.ehviewer.updater.AppUpdater
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.ExceptionUtils
@@ -323,85 +324,87 @@ class MainActivity : EhActivity() {
LocalDrawerLockHandle provides lockDrawerHandle,
LocalSnackbarHostState provides snackbarState,
) {
Scaffold(snackbarHost = { SnackbarHost(snackbarState) }) {
LocalTouchSlopProvider(Settings.touchSlopFactor.toFloat()) {
ModalNavigationDrawer(
drawerContent = {
ModalDrawerSheet(
modifier = Modifier.widthIn(max = (configuration.screenWidthDp - 56).dp),
windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Start),
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier.verticalScroll(scrollState)
.windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)),
ProvideVectorPainterCache {
Scaffold(snackbarHost = { SnackbarHost(snackbarState) }) {
LocalTouchSlopProvider(Settings.touchSlopFactor.toFloat()) {
ModalNavigationDrawer(
drawerContent = {
ModalDrawerSheet(
modifier = Modifier.widthIn(max = (configuration.screenWidthDp - 56).dp),
windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Start),
) {
Image(
painter = painterResource(id = R.drawable.sadpanda_low_poly),
contentDescription = null,
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
contentScale = ContentScale.FillWidth,
)
navItems.forEach { (direction, stringId, icon) ->
val scrollState = rememberScrollState()
Column(
modifier = Modifier.verticalScroll(scrollState)
.windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)),
) {
Image(
painter = painterResource(id = R.drawable.sadpanda_low_poly),
contentDescription = null,
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
contentScale = ContentScale.FillWidth,
)
navItems.forEach { (direction, stringId, icon) ->
NavigationDrawerItem(
label = {
Text(text = stringResource(id = stringId))
},
selected = currentDestination === direction,
onClick = {
navController.navigate(direction.route)
closeDrawer()
},
modifier = Modifier.padding(horizontal = 12.dp),
icon = {
IconFix(imageVector = icon, contentDescription = null)
},
)
}
NavigationDrawerItem(
label = {
Text(text = stringResource(id = stringId))
Text(text = stringResource(id = R.string.settings))
},
selected = currentDestination === direction,
selected = false,
onClick = {
navController.navigate(direction.route)
closeDrawer()
closeDrawer { startActivity(Intent(applicationContext, ConfigureActivity::class.java)) }
},
modifier = Modifier.padding(horizontal = 12.dp),
icon = {
IconFix(imageVector = icon, contentDescription = null)
Icon(imageVector = Icons.Default.Settings, contentDescription = null)
},
)
}
NavigationDrawerItem(
label = {
Text(text = stringResource(id = R.string.settings))
},
selected = false,
onClick = {
closeDrawer { startActivity(Intent(applicationContext, ConfigureActivity::class.java)) }
},
modifier = Modifier.padding(horizontal = 12.dp),
icon = {
Icon(imageVector = Icons.Default.Settings, contentDescription = null)
},
)
}
}
},
drawerState = navDrawerState,
gesturesEnabled = !drawerLocked || navDrawerState.isOpen,
) {
val sheet = sideSheet.firstOrNull()
SideDrawer(
drawerContent = {
if (sheet != null) {
ModalDrawerSheet(
modifier = Modifier.widthIn(max = (configuration.screenWidthDp - 112).dp),
drawerShape = ShapeDefaults.Large.copy(topEnd = CornerSize(0), bottomEnd = CornerSize(0)),
windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top + WindowInsetsSides.End),
) {
CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
sheet(sideSheetState)
}
}
}
},
drawerState = sideSheetState,
gesturesEnabled = sheet != null && !drawerLocked,
drawerState = navDrawerState,
gesturesEnabled = !drawerLocked || navDrawerState.isOpen,
) {
CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
DestinationsNavHost(
navGraph = NavGraphs.root,
startRoute = navItems[Settings.launchPage].first,
engine = rememberNavHostEngine(rootDefaultAnimations = ehNavAnim),
navController = navController,
)
val sheet = sideSheet.firstOrNull()
SideDrawer(
drawerContent = {
if (sheet != null) {
ModalDrawerSheet(
modifier = Modifier.widthIn(max = (configuration.screenWidthDp - 112).dp),
drawerShape = ShapeDefaults.Large.copy(topEnd = CornerSize(0), bottomEnd = CornerSize(0)),
windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Top + WindowInsetsSides.End),
) {
CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
sheet(sideSheetState)
}
}
}
},
drawerState = sideSheetState,
gesturesEnabled = sheet != null && !drawerLocked,
) {
CompositionLocalProvider(LocalViewConfiguration provides viewConfiguration) {
DestinationsNavHost(
navGraph = NavGraphs.root,
startRoute = navItems[Settings.launchPage].first,
engine = rememberNavHostEngine(rootDefaultAnimations = ehNavAnim),
navController = navController,
)
}
}
}
}
@@ -4,14 +4,15 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.ReusableComposition
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
@@ -21,35 +22,33 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter
@Stable
@Composable
private fun rememberNeverRecomposeVectorPainter(image: ImageVector): VectorPainter {
val context = rememberCompositionContext()
var b by remember(image) { mutableStateOf<VectorPainter?>(null) }
val composition = remember(image) {
val composition = ReusableComposition(
object : Applier<Unit> {
override val current = Unit
override fun clear() = Unit
override fun move(from: Int, to: Int, count: Int) = Unit
override fun remove(index: Int, count: Int) = Unit
override fun up() = Unit
override fun insertTopDown(index: Int, instance: Unit) = Unit
override fun insertBottomUp(index: Int, instance: Unit) = Unit
override fun down(node: Unit) = Unit
},
context,
)
composition.apply {
setContent {
b = rememberVectorPainter(image = image)
composition.deactivate()
val cache = LocalVectorPainterCache.current
return remember(image) {
val (context, map) = cache
map.computeIfAbsent(image) {
lateinit var b: VectorPainter
val composition = ReusableComposition(
object : Applier<Unit> {
override val current = Unit
override fun clear() = Unit
override fun move(from: Int, to: Int, count: Int) = Unit
override fun remove(index: Int, count: Int) = Unit
override fun up() = Unit
override fun insertTopDown(index: Int, instance: Unit) = Unit
override fun insertBottomUp(index: Int, instance: Unit) = Unit
override fun down(node: Unit) = Unit
},
context,
)
composition.apply {
setContent {
b = rememberVectorPainter(image = image)
composition.deactivate()
}
}
}
composition to b
}.second
}
DisposableEffect(composition) {
onDispose {
composition.dispose()
}
}
return b!!
}
@Composable
@@ -66,3 +65,24 @@ fun IconFix(
tint = tint,
)
}
@Composable
fun ProvideVectorPainterCache(content: @Composable () -> Unit) {
val compositionContext = rememberCompositionContext()
val cache = remember { mutableMapOf<ImageVector, Pair<Composition, VectorPainter>>() }
val provides = remember { compositionContext to cache }
CompositionLocalProvider(
LocalVectorPainterCache provides provides,
content = content,
)
DisposableEffect(Unit) {
onDispose {
cache.values.forEach { (composition, _) -> composition.dispose() }
cache.clear()
}
}
}
typealias VectorPainterCache = Pair<CompositionContext, MutableMap<ImageVector, Pair<Composition, VectorPainter>>>
val LocalVectorPainterCache = compositionLocalOf<VectorPainterCache> { error("CompositionLocal LocalSideSheetState not present!") }