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
This commit is contained in:
OneWay committed 2026-10-14 16:06:12 +08:00
1 parent c2ddfdf89d
commit 76ea8ba9a0
1 file changed
+27 -15
@@ -95,22 +95,20 @@ data class GalleryUiState(
val error: String? = null, val error: String? = null,
) { ) {
val isSelectionMode: Boolean = selectedPhotoUris.isNotEmpty() val isSelectionMode: Boolean = selectedPhotoUris.isNotEmpty()
val selectedPhotos: List<Photo> = photos.filter { it.uri.toString() in selectedPhotoUris }
val sortedSubfolders: List<FolderNode> = if (sortAscending) { val selectedPhotos: List<Photo> by lazy {
subfolders.sortedBy { it.name.lowercase() } photos.filter { it.uri.toString() in selectedPhotoUris }
} else {
subfolders.sortedByDescending { it.name.lowercase() }
} }
val sortedPhotos: List<Photo> = if (sortAscending) {
photos.sortedBy { it.name.lowercase() } val selectablePhotoUris: Set<String> by lazy {
} else { photos.asSequence()
photos.sortedByDescending { it.name.lowercase() }
}
val selectablePhotoUris: Set<String> = photos
.filter { it.kind == GalleryFileKind.Photo } .filter { it.kind == GalleryFileKind.Photo }
.map { it.uri.toString() } .mapTo(HashSet()) { it.uri.toString() }
.toSet() }
val allPhotosSelected: Boolean = selectablePhotoUris.isNotEmpty() && selectedPhotoUris.containsAll(selectablePhotoUris)
val allPhotosSelected: Boolean by lazy {
selectablePhotoUris.isNotEmpty() && selectedPhotoUris.containsAll(selectablePhotoUris)
}
} }
@HiltViewModel @HiltViewModel
@@ -342,18 +340,31 @@ class GalleryViewModel @Inject constructor(
fun clearSelection() { fun clearSelection() {
dragSelectionBaseline = emptySet() dragSelectionBaseline = emptySet()
dragSelectableCache = null
uiState.value = uiState.value.copy(selectedPhotoUris = emptySet()) uiState.value = uiState.value.copy(selectedPhotoUris = emptySet())
} }
private var dragSelectionBaseline: Set<String> = emptySet() private var dragSelectionBaseline: Set<String> = emptySet()
private var dragSelectableCache: Pair<List<Photo>, Set<String>>? = null
private fun currentSelectablePhotoUris(): Set<String> {
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) { fun beginDragSelection(anchorUri: String) {
dragSelectionBaseline = uiState.value.selectedPhotoUris + anchorUri dragSelectionBaseline = uiState.value.selectedPhotoUris + anchorUri
uiState.value = uiState.value.copy(selectedPhotoUris = dragSelectionBaseline) uiState.value = uiState.value.copy(selectedPhotoUris = dragSelectionBaseline)
} }
fun updateDragSelection(rangeUris: Set<String>) { fun updateDragSelection(rangeUris: Set<String>) {
val selectable = uiState.value.selectablePhotoUris val selectable = currentSelectablePhotoUris()
uiState.value = uiState.value.copy( uiState.value = uiState.value.copy(
selectedPhotoUris = dragSelectionBaseline + rangeUris.intersect(selectable), selectedPhotoUris = dragSelectionBaseline + rangeUris.intersect(selectable),
) )
@@ -361,6 +372,7 @@ class GalleryViewModel @Inject constructor(
fun endDragSelection() { fun endDragSelection() {
dragSelectionBaseline = emptySet() dragSelectionBaseline = emptySet()
dragSelectableCache = null
} }
fun zipSelectedPhotosAndShare() { fun zipSelectedPhotosAndShare() {