From 76ea8ba9a0a98cf9e8ff3e5d3309add9abce88c4 Mon Sep 17 00:00:00 2001 From: OneWay Date: Wed, 14 Oct 2026 16:06:12 +0800 Subject: [PATCH] Make GalleryUiState derivations lazy and drop dead sorts copy() ran on every state update (each drag-select step included) and eagerly recomputed five derived properties: two unused O(N log N) sorts, an O(N) selection filter, an O(N) selectable-URI set and its containsAll check. - Remove sortedPhotos/sortedSubfolders: never read (the screen sorts with its own remember() calls) - selectedPhotos/selectablePhotoUris/allPhotosSelected become by lazy: computed at most once per instance, only when actually read; body delegates stay out of generated copy()/equals(), @Immutable semantics hold - Memoize selectablePhotoUris across drag-select steps in the ViewModel keyed by photos list identity, so a drag step no longer recomputes the O(N) set --- .../onecamera/ui/gallery/GalleryViewModel.kt | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) 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 f8c202d..031f15e 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 @@ -95,22 +95,20 @@ data class GalleryUiState( val error: String? = null, ) { val isSelectionMode: Boolean = selectedPhotoUris.isNotEmpty() - val selectedPhotos: List = photos.filter { it.uri.toString() in selectedPhotoUris } - val sortedSubfolders: List = if (sortAscending) { - subfolders.sortedBy { it.name.lowercase() } - } else { - subfolders.sortedByDescending { it.name.lowercase() } + + val selectedPhotos: List by lazy { + photos.filter { it.uri.toString() in selectedPhotoUris } } - val sortedPhotos: List = if (sortAscending) { - photos.sortedBy { it.name.lowercase() } - } else { - photos.sortedByDescending { it.name.lowercase() } + + val selectablePhotoUris: Set by lazy { + photos.asSequence() + .filter { it.kind == GalleryFileKind.Photo } + .mapTo(HashSet()) { it.uri.toString() } + } + + val allPhotosSelected: Boolean by lazy { + selectablePhotoUris.isNotEmpty() && selectedPhotoUris.containsAll(selectablePhotoUris) } - val selectablePhotoUris: Set = photos - .filter { it.kind == GalleryFileKind.Photo } - .map { it.uri.toString() } - .toSet() - val allPhotosSelected: Boolean = selectablePhotoUris.isNotEmpty() && selectedPhotoUris.containsAll(selectablePhotoUris) } @HiltViewModel @@ -342,18 +340,31 @@ class GalleryViewModel @Inject constructor( fun clearSelection() { dragSelectionBaseline = emptySet() + dragSelectableCache = null uiState.value = uiState.value.copy(selectedPhotoUris = emptySet()) } private var dragSelectionBaseline: Set = emptySet() + private var dragSelectableCache: Pair, Set>? = null + + private fun currentSelectablePhotoUris(): Set { + val photos = uiState.value.photos + dragSelectableCache?.let { (cachedPhotos, uris) -> + if (cachedPhotos === photos) return uris + } + val uris = uiState.value.selectablePhotoUris + dragSelectableCache = photos to uris + return uris + } + fun beginDragSelection(anchorUri: String) { dragSelectionBaseline = uiState.value.selectedPhotoUris + anchorUri uiState.value = uiState.value.copy(selectedPhotoUris = dragSelectionBaseline) } fun updateDragSelection(rangeUris: Set) { - val selectable = uiState.value.selectablePhotoUris + val selectable = currentSelectablePhotoUris() uiState.value = uiState.value.copy( selectedPhotoUris = dragSelectionBaseline + rangeUris.intersect(selectable), ) @@ -361,6 +372,7 @@ class GalleryViewModel @Inject constructor( fun endDragSelection() { dragSelectionBaseline = emptySet() + dragSelectableCache = null } fun zipSelectedPhotosAndShare() {