import SwiftUI struct PostDetailsCarouselView: View { @EnvironmentObject var manager: BooruManager @EnvironmentObject var settings: SettingsManager let posts: [BooruPost] let focusedPost: BooruPost? @Binding var loadingStage: BooruPostLoadingState @State private var currentIndex: Int? private let cacheManager = ImageCacheManager.shared init( posts: [BooruPost], loadingStage: Binding, focusedPost: BooruPost? = nil ) { self.posts = posts self.focusedPost = focusedPost _loadingStage = loadingStage if let focused = focusedPost, let index = posts.firstIndex(where: { $0.id == focused.id }) { self._currentIndex = State(initialValue: index) } else { self._currentIndex = State(initialValue: 0) } } func imageURL(post: BooruPost) -> URL? { switch settings.detailViewQuality { case .preview: post.previewURL case .sample: post.sampleURL case .original: post.fileURL } } var body: some View { ScrollView(.horizontal) { LazyHStack(spacing: 0) { ForEach(Array(posts.enumerated()), id: \.offset) { index, post in PostDetailsCarouselItemView( post: post, index: index, loadingStage: $loadingStage, imageURL: imageURL ) #if os(iOS) .frame(width: UIScreen.main.bounds.width) #endif } } .scrollTargetLayout() } .scrollPosition(id: $currentIndex) .scrollIndicators(.hidden) .scrollTargetBehavior(.paging) .onChange(of: currentIndex) { guard let currentIndex else { return } Task(priority: .utility) { if currentIndex == posts.count - 1 { await manager.loadNextPage() } } preloadNearbyImages() } .onAppear(perform: preloadNearbyImages) } private func preloadNearbyImages() { let preloadRange = settings.preloadedCarouselImages guard preloadRange > 0 else { return } guard let currentIndex else { return } let startIndex = max(0, currentIndex - preloadRange) let endIndex = min(posts.count - 1, currentIndex + preloadRange) var urlsToPreload: [URL] = [] for index in startIndex...endIndex { if let url = imageURL(post: posts[index]) { urlsToPreload.append(url) } urlsToPreload.append(posts[index].previewURL) } cacheManager.preloadImages(urlsToPreload) } }