Apply Compose performance audit fixes
- FolderTreePickerSheet: add contentType to lazy folder items for slot reuse - GalleryScreen: use plain remember instead of derivedStateOf over non-state inputs; defer photo fade alpha to Draw phase via graphicsLayer; react to zoom threshold with snapshotFlow instead of per-frame effect relaunch - GalleryViewModel: drop @Immutable from MovePhotoSheetState so Strong Skipping compares the lazy feed by O(1) reference equality - SystemCameraCapture: move temp-file disk I/O off the main thread
This commit is contained in:
1 parent
9910ba6917
commit
c2ddfdf89d
4 files changed
+28
-22
No files matched your search
@@ -37,7 +37,9 @@ import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
import com.example.onecamera.R
|
||||
import com.example.onecamera.ui.components.OneCameraEmptyState
|
||||
import com.example.onecamera.ui.components.OneCameraScreenPadding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
@@ -111,6 +113,7 @@ fun SystemCameraCapture(
|
||||
LaunchedEffect(hasLaunchedCamera) {
|
||||
if (!hasLaunchedCamera) {
|
||||
viewModel.clearLastSaveResult()
|
||||
val (preparedPath, preparedUri) = withContext(Dispatchers.IO) {
|
||||
val tempDir = File(context.cacheDir, "camera_captures").apply { mkdirs() }
|
||||
|
||||
tempDir.listFiles()?.forEach { runCatching { it.delete() } }
|
||||
@@ -122,12 +125,14 @@ fun SystemCameraCapture(
|
||||
"${context.packageName}.fileprovider",
|
||||
tempFile,
|
||||
)
|
||||
tempPhotoUriString = uri.toString()
|
||||
tempPhotoPath = tempFile.absolutePath
|
||||
tempFile.absolutePath to uri.toString()
|
||||
}
|
||||
tempPhotoUriString = preparedUri
|
||||
tempPhotoPath = preparedPath
|
||||
hasLaunchedCamera = true
|
||||
hasReceivedCameraResult = false
|
||||
launchUptimeMillis = SystemClock.elapsedRealtime()
|
||||
takePictureLauncher.launch(uri)
|
||||
takePictureLauncher.launch(Uri.parse(preparedUri))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +148,7 @@ fun SystemCameraCapture(
|
||||
tempPhotoUriString != null
|
||||
) {
|
||||
val tempLength = tempPhotoPath?.let { path ->
|
||||
runCatching { File(path).length() }.getOrNull()
|
||||
runCatching { withContext(Dispatchers.IO) { File(path).length() } }.getOrNull()
|
||||
} ?: 0L
|
||||
val tempPhotoUri = tempPhotoUriString?.let(Uri::parse)
|
||||
if (tempLength > 0L && tempPhotoUri != null) {
|
||||
|
||||
@@ -105,7 +105,11 @@ fun FolderTreePickerSheet(
|
||||
.fillMaxWidth()
|
||||
.weight(1f, fill = false),
|
||||
) {
|
||||
items(state.folders, key = { it.uri }) { folder ->
|
||||
items(
|
||||
items = state.folders,
|
||||
key = { it.uri },
|
||||
contentType = { "folder" },
|
||||
) { folder ->
|
||||
val isCurrent = currentFolderUri == folder.uri.toString()
|
||||
val isSelected = selectedFolderUri == folder.uri.toString()
|
||||
ListItem(
|
||||
|
||||
@@ -41,16 +41,16 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@@ -86,6 +86,7 @@ import com.example.onecamera.ui.folders.RenameFolderDialog
|
||||
import com.github.panpf.zoomimage.CoilZoomAsyncImage
|
||||
import com.github.panpf.zoomimage.rememberCoilZoomState
|
||||
import com.github.panpf.zoomimage.zoom.GestureType
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -198,11 +199,9 @@ fun GalleryScreen(
|
||||
uiState.subfolders.sortedByDescending { it.name.lowercase() }
|
||||
}
|
||||
}
|
||||
val sortedGalleryPhotos by remember(sortedPhotos) {
|
||||
derivedStateOf {
|
||||
val sortedGalleryPhotos = remember(sortedPhotos) {
|
||||
sortedPhotos.filter { it.kind == GalleryFileKind.Photo }
|
||||
}
|
||||
}
|
||||
|
||||
when (val dialog = uiState.dialog) {
|
||||
GalleryDialog.CreateSubfolder -> {
|
||||
@@ -537,11 +536,9 @@ internal fun FullscreenPhotoPagerContent(
|
||||
onDismiss: () -> Unit,
|
||||
onDelete: (Photo) -> Unit,
|
||||
) {
|
||||
val initialPage by remember(photos, initialPhoto.uri) {
|
||||
derivedStateOf {
|
||||
val initialPage = remember(photos, initialPhoto.uri) {
|
||||
photos.indexOfFirst { it.uri == initialPhoto.uri }.coerceAtLeast(0)
|
||||
}
|
||||
}
|
||||
val pagerState = rememberPagerState(initialPage = initialPage) { photos.size.coerceAtLeast(1) }
|
||||
|
||||
var isZoomed by remember { mutableStateOf(false) }
|
||||
@@ -646,7 +643,6 @@ private fun ZoomablePhoto(
|
||||
animationSpec = tween(durationMillis = fadeMs),
|
||||
label = "fullAlpha",
|
||||
)
|
||||
val scale by remember { derivedStateOf { zoomState.zoomable.transform.scaleX } }
|
||||
|
||||
LaunchedEffect(photo.uri) {
|
||||
|
||||
@@ -663,25 +659,27 @@ private fun ZoomablePhoto(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(scale) {
|
||||
val zoomed = scale > 1.01f
|
||||
|
||||
LaunchedEffect(zoomState) {
|
||||
snapshotFlow { zoomState.zoomable.transform.scaleX > 1.01f }
|
||||
.distinctUntilChanged()
|
||||
.collect { zoomed ->
|
||||
onZoomChange(zoomed)
|
||||
zoomState.subsampling.setDisabled(!zoomed)
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
|
||||
if (fullAlpha < 1f) {
|
||||
AsyncImage(
|
||||
model = remember(photo.uri, context, fadeMs) {
|
||||
thumbnailDraftRequest(context, photo.uri, crossfadeMillis = fadeMs)
|
||||
},
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer { alpha = 1f - fullAlpha },
|
||||
)
|
||||
}
|
||||
CoilZoomAsyncImage(
|
||||
model = fullImageRequest,
|
||||
contentDescription = photo.name,
|
||||
@@ -690,7 +688,7 @@ private fun ZoomablePhoto(
|
||||
onError = { isFullReady = false },
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.alpha(fullAlpha),
|
||||
.graphicsLayer { alpha = fullAlpha },
|
||||
zoomState = zoomState,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ sealed interface GalleryDialog {
|
||||
data class DeleteSubfolder(val folder: FolderNode) : GalleryDialog
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class MovePhotoSheetState(
|
||||
val file: Photo? = null,
|
||||
val files: List<Photo> = emptyList(),
|
||||
|
||||
Reference in new issue
Block a user