From 0a2268d4de99d3881aa97936b98d15ad06beae98 Mon Sep 17 00:00:00 2001 From: GG Bond Date: Fri, 8 Dec 2023 12:06:01 +0800 Subject: [PATCH] Allow VectorPainter to be cached --- .../com/hippo/ehviewer/ui/MainActivity.kt | 133 +++++++++--------- .../com/hippo/ehviewer/ui/tools/Workaround.kt | 80 +++++++---- 2 files changed, 118 insertions(+), 95 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt index 8e1462a14..a207b1e38 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt @@ -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, + ) + } } } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Workaround.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Workaround.kt index dae13ac5a..e72a3e727 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Workaround.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Workaround.kt @@ -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(null) } - val composition = remember(image) { - val composition = ReusableComposition( - object : Applier { - 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 { + 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>() } + 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>> + +val LocalVectorPainterCache = compositionLocalOf { error("CompositionLocal LocalSideSheetState not present!") }