diff --git a/app/src/main/java/com/example/onecamera/ui/camera/SystemCameraCapture.kt b/app/src/main/java/com/example/onecamera/ui/camera/SystemCameraCapture.kt index 3e692c4..268a2fc 100644 --- a/app/src/main/java/com/example/onecamera/ui/camera/SystemCameraCapture.kt +++ b/app/src/main/java/com/example/onecamera/ui/camera/SystemCameraCapture.kt @@ -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,23 +113,26 @@ fun SystemCameraCapture( LaunchedEffect(hasLaunchedCamera) { if (!hasLaunchedCamera) { viewModel.clearLastSaveResult() - val tempDir = File(context.cacheDir, "camera_captures").apply { mkdirs() } + val (preparedPath, preparedUri) = withContext(Dispatchers.IO) { + val tempDir = File(context.cacheDir, "camera_captures").apply { mkdirs() } - tempDir.listFiles()?.forEach { runCatching { it.delete() } } - val tempFile = File(tempDir, "capture_${System.currentTimeMillis()}.jpg").apply { - createNewFile() + tempDir.listFiles()?.forEach { runCatching { it.delete() } } + val tempFile = File(tempDir, "capture_${System.currentTimeMillis()}.jpg").apply { + createNewFile() + } + val uri = FileProvider.getUriForFile( + context, + "${context.packageName}.fileprovider", + tempFile, + ) + tempFile.absolutePath to uri.toString() } - val uri = FileProvider.getUriForFile( - context, - "${context.packageName}.fileprovider", - tempFile, - ) - tempPhotoUriString = uri.toString() - tempPhotoPath = tempFile.absolutePath + 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) { diff --git a/app/src/main/java/com/example/onecamera/ui/components/FolderTreePickerSheet.kt b/app/src/main/java/com/example/onecamera/ui/components/FolderTreePickerSheet.kt index 42dc73f..dc11391 100644 --- a/app/src/main/java/com/example/onecamera/ui/components/FolderTreePickerSheet.kt +++ b/app/src/main/java/com/example/onecamera/ui/components/FolderTreePickerSheet.kt @@ -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( diff --git a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt index e81d0d7..db68067 100644 --- a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt +++ b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt @@ -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,10 +199,8 @@ fun GalleryScreen( uiState.subfolders.sortedByDescending { it.name.lowercase() } } } - val sortedGalleryPhotos by remember(sortedPhotos) { - derivedStateOf { - sortedPhotos.filter { it.kind == GalleryFileKind.Photo } - } + val sortedGalleryPhotos = remember(sortedPhotos) { + sortedPhotos.filter { it.kind == GalleryFileKind.Photo } } when (val dialog = uiState.dialog) { @@ -537,10 +536,8 @@ internal fun FullscreenPhotoPagerContent( onDismiss: () -> Unit, onDelete: (Photo) -> Unit, ) { - val initialPage by remember(photos, initialPhoto.uri) { - derivedStateOf { - photos.indexOfFirst { it.uri == initialPhoto.uri }.coerceAtLeast(0) - } + val initialPage = remember(photos, initialPhoto.uri) { + photos.indexOfFirst { it.uri == initialPhoto.uri }.coerceAtLeast(0) } val pagerState = rememberPagerState(initialPage = initialPage) { photos.size.coerceAtLeast(1) } @@ -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 - - onZoomChange(zoomed) - zoomState.subsampling.setDisabled(!zoomed) + 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(), - ) - } + AsyncImage( + model = remember(photo.uri, context, fadeMs) { + thumbnailDraftRequest(context, photo.uri, crossfadeMillis = fadeMs) + }, + contentDescription = null, + contentScale = ContentScale.Fit, + 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, ) } diff --git a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt index 16b3fe2..f8c202d 100644 --- a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt +++ b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt @@ -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 = emptyList(),