diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift index 19487f0d..ae018100 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift @@ -166,6 +166,7 @@ public struct StatusRowMediaPreviewView: View { .frame(width: newSize.width, height: newSize.height) } } + .processors([ImageProcessors.Resize(size: .init(width: newSize.width, height: newSize.height))]) .frame(width: newSize.width, height: newSize.height) case .gifv, .video, .audio: @@ -205,13 +206,14 @@ public struct StatusRowMediaPreviewView: View { GeometryReader { proxy in switch type { case .image: + let width = isNotifications ? imageMaxHeight : proxy.frame(in: .local).width ZStack(alignment: .bottomTrailing) { - LazyImage(url: attachment.url) { state in + LazyImage(url: attachment.previewUrl ?? attachment.url) { state in if let image = state.imageContainer?.image { SwiftUI.Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fill) - .frame(maxWidth: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width) + .frame(maxWidth: width) .frame(maxHeight: imageMaxHeight) .clipped() .cornerRadius(4) @@ -219,9 +221,10 @@ public struct StatusRowMediaPreviewView: View { RoundedRectangle(cornerRadius: 4) .fill(Color.gray) .frame(maxHeight: imageMaxHeight) - .frame(maxWidth: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width) + .frame(maxWidth: width) } } + .processors([ImageProcessors.Resize(size: .init(width: width, height: imageMaxHeight))]) if sensitive { cornerSensitiveButton } diff --git a/Packages/Timeline/Sources/Timeline/TimelinePrefetcher.swift b/Packages/Timeline/Sources/Timeline/TimelinePrefetcher.swift new file mode 100644 index 00000000..166dec1a --- /dev/null +++ b/Packages/Timeline/Sources/Timeline/TimelinePrefetcher.swift @@ -0,0 +1,42 @@ +import SwiftUI +import UIKit +import Models +import Nuke + +final class TimelinePrefetcher: NSObject, ObservableObject, UICollectionViewDataSourcePrefetching { + private let prefetcher = ImagePrefetcher() + + weak var viewModel: TimelineViewModel? + + func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { + let imageURLs = getImageURLs(for: indexPaths) + prefetcher.startPrefetching(with: imageURLs) + } + + func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { + let imageURLs = getImageURLs(for: indexPaths) + prefetcher.stopPrefetching(with: imageURLs) + } + + private func getImageURLs(for indexPaths: [IndexPath]) -> [URL] { + guard let viewModel, case .display(let statuses, _) = viewModel.statusesState else { + return [] + } + return indexPaths.compactMap { + $0.row < statuses.endIndex ? statuses[$0.row] : nil + }.flatMap(getImages) + } +} + +private func getImages(for status: Status) -> [URL] { + var urls = status.mediaAttachments.compactMap { + if $0.supportedType == .image { + return status.mediaAttachments.count > 1 ? $0.previewUrl ?? $0.url : $0.url + } + return nil + } + if let url = status.card?.image { + urls.append(url) + } + return urls +} diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift index 4d6ad52f..e818e039 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift @@ -20,6 +20,7 @@ public struct TimelineView: View { @EnvironmentObject private var routerPath: RouterPath @StateObject private var viewModel = TimelineViewModel() + @StateObject private var prefetcher = TimelinePrefetcher() @State private var wasBackgrounded: Bool = false @State private var collectionView: UICollectionView? @@ -53,10 +54,12 @@ public struct TimelineView: View { .listStyle(.plain) .scrollContentBackground(.hidden) .background(theme.primaryBackgroundColor) - .introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, - customize: { (collectionView: UICollectionView) in - self.collectionView = collectionView - }) + .introspect(selector: TargetViewSelector.ancestorOrSiblingContaining) { (collectionView: UICollectionView) in + self.collectionView = collectionView + self.prefetcher.viewModel = viewModel + collectionView.isPrefetchingEnabled = true + collectionView.prefetchDataSource = self.prefetcher + } if viewModel.pendingStatusesEnabled { PendingStatusesObserverView(observer: viewModel.pendingStatusesObserver) }