From 422f3e9027bb3cf147ca07ef588ba1c5c9ec5918 Mon Sep 17 00:00:00 2001 From: Kieran Klukas Date: Sat, 30 May 2026 14:41:38 -0400 Subject: [PATCH] feat: load adjacent groups --- cull/Models/CullSession.swift | 21 ++++++++++++++++++++ cull/Views/PhotoViewer.swift | 36 ++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/cull/Models/CullSession.swift b/cull/Models/CullSession.swift index 41eeeb3..b419b0f 100644 --- a/cull/Models/CullSession.swift +++ b/cull/Models/CullSession.swift @@ -259,6 +259,27 @@ final class CullSession { } } + /// Returns the first visible photo from the next and previous `groupCount` groups. + /// Interleaved closest-first so nearest groups are cached with highest priority. + func adjacentGroupRepresentatives(groupCount: Int = 15) -> [Photo] { + guard !groups.isEmpty else { return [] } + var result: [Photo] = [] + for offset in 1...groupCount { + // Next group (closest first due to iteration order) + let nextIdx = (selectedGroupIndex + offset) % groups.count + if let first = groups[nextIdx].photos.first(where: { !isPhotoFiltered($0) }) { + result.append(first) + } + // Previous group + let prevIdx = (selectedGroupIndex - offset + groups.count) % groups.count + if prevIdx != nextIdx, + let first = groups[prevIdx].photos.first(where: { !isPhotoFiltered($0) }) { + result.append(first) + } + } + return result + } + // MARK: - Culling Actions private func applyPhotoState(_ photo: Photo, rating: Int, flag: PhotoFlag, actionName: String) { diff --git a/cull/Views/PhotoViewer.swift b/cull/Views/PhotoViewer.swift index 7e3c88f..6498543 100644 --- a/cull/Views/PhotoViewer.swift +++ b/cull/Views/PhotoViewer.swift @@ -290,15 +290,25 @@ struct PhotoViewer: View { } } - // Preload window fanning out from current position (closest first) + // Preload window: current → interleaved group reps + linear neighbors (closest first) guard !Task.isCancelled, displayedPhotoID == photoID else { return } + let groupReps = session.adjacentGroupRepresentatives(groupCount: 15) let ahead = session.photosAhead(lookaheadCount) let behind = session.photosBehind(lookbehindCount) + + // Interleave: 2 group reps per linear neighbor so group scanning gets priority var fanOut: [Photo] = [] - let maxLen = max(ahead.count, behind.count) - for i in 0..