#44 Prefetch images stright after download statuses
This commit is contained in:
parent
c707b136c2
commit
1d0322ecc2
|
@ -107,6 +107,18 @@ public extension StatusModel {
|
|||
}
|
||||
}
|
||||
|
||||
public extension [StatusModel] {
|
||||
func getAllImagesUrls() -> [URL] {
|
||||
var urls: [URL] = []
|
||||
|
||||
for status in self {
|
||||
urls.append(contentsOf: status.mediaAttachments.map({ $0.url }))
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
}
|
||||
|
||||
public extension [Status] {
|
||||
func toStatusModels() -> [StatusModel] {
|
||||
self
|
||||
|
|
|
@ -9,6 +9,7 @@ import CoreData
|
|||
import PixelfedKit
|
||||
import ClientKit
|
||||
import ServicesKit
|
||||
import Nuke
|
||||
|
||||
/// Service responsible for managing home timeline.
|
||||
public class HomeTimelineService {
|
||||
|
@ -16,6 +17,7 @@ public class HomeTimelineService {
|
|||
private init() { }
|
||||
|
||||
private let defaultAmountOfDownloadedStatuses = 40
|
||||
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
|
||||
|
||||
public func loadOnBottom(for account: AccountModel) async throws -> Int {
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
|
@ -29,26 +31,36 @@ public class HomeTimelineService {
|
|||
}
|
||||
|
||||
// Load data on bottom of the list.
|
||||
let newStatuses = try await self.load(for: account, on: backgroundContext, maxId: oldestStatus.id)
|
||||
let allStatusesFromApi = try await self.load(for: account, on: backgroundContext, maxId: oldestStatus.id)
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
// Start prefetching images.
|
||||
self.prefetch(statuses: allStatusesFromApi)
|
||||
|
||||
// Return amount of newly downloaded statuses.
|
||||
return newStatuses.count
|
||||
return allStatusesFromApi.count
|
||||
}
|
||||
|
||||
public func refreshTimeline(for account: AccountModel) async throws -> String? {
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
// Retrieve newest visible status (last visible by user).
|
||||
let dbNewestStatus = StatusDataHandler.shared.getMaximumStatus(accountId: account.id, viewContext: backgroundContext)
|
||||
let lastSeenStatusId = dbNewestStatus?.rebloggedStatusId ?? dbNewestStatus?.id
|
||||
|
||||
// Refresh/load home timeline (refreshing on top downloads always first 40 items).
|
||||
// When Apple introduce good way to show new items without scroll to top then we can change that method.
|
||||
let lastSeenStatusId = try await self.refresh(for: account, on: backgroundContext)
|
||||
let allStatusesFromApi = try await self.refresh(for: account, on: backgroundContext)
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
// Start prefetching images.
|
||||
self.prefetch(statuses: allStatusesFromApi)
|
||||
|
||||
// Return id of last seen status.
|
||||
return lastSeenStatusId
|
||||
}
|
||||
|
@ -134,19 +146,15 @@ public class HomeTimelineService {
|
|||
return amountOfStatuses
|
||||
}
|
||||
|
||||
private func refresh(for account: AccountModel, on backgroundContext: NSManagedObjectContext) async throws -> String? {
|
||||
private func refresh(for account: AccountModel, on backgroundContext: NSManagedObjectContext) async throws -> [Status] {
|
||||
guard let accessToken = account.accessToken else {
|
||||
return nil
|
||||
return []
|
||||
}
|
||||
|
||||
// Retrieve statuses from API.
|
||||
let client = PixelfedClient(baseURL: account.serverUrl).getAuthenticated(token: accessToken)
|
||||
let statuses = try await client.getHomeTimeline(limit: self.defaultAmountOfDownloadedStatuses)
|
||||
|
||||
// Retrieve newest visible status (last visible by user).
|
||||
let dbNewestStatus = StatusDataHandler.shared.getMaximumStatus(accountId: account.id, viewContext: backgroundContext)
|
||||
let lastSeenStatusId = dbNewestStatus?.rebloggedStatusId ?? dbNewestStatus?.id
|
||||
|
||||
// Update all existing statuses in database.
|
||||
for status in statuses {
|
||||
if let dbStatus = StatusDataHandler.shared.getStatusData(accountId: account.id, statusId: status.id, viewContext: backgroundContext) {
|
||||
|
@ -196,7 +204,8 @@ public class HomeTimelineService {
|
|||
_ = try await self.add(statusesToAdd, for: account, on: backgroundContext)
|
||||
}
|
||||
|
||||
return lastSeenStatusId
|
||||
// Return all statuses downloaded from API.
|
||||
return statuses
|
||||
}
|
||||
|
||||
private func load(for account: AccountModel,
|
||||
|
@ -213,13 +222,16 @@ public class HomeTimelineService {
|
|||
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: self.defaultAmountOfDownloadedStatuses)
|
||||
|
||||
// Save statuses in database.
|
||||
return try await self.add(statuses, for: account, on: backgroundContext)
|
||||
try await self.add(statuses, for: account, on: backgroundContext)
|
||||
|
||||
// Return all statuses downloaded from API.
|
||||
return statuses
|
||||
}
|
||||
|
||||
private func add(_ statuses: [Status],
|
||||
for account: AccountModel,
|
||||
on backgroundContext: NSManagedObjectContext
|
||||
) async throws -> [Status] {
|
||||
) async throws {
|
||||
|
||||
guard let accountDataFromDb = AccountDataHandler.shared.getAccountData(accountId: account.id, viewContext: backgroundContext) else {
|
||||
throw DatabaseError.cannotDownloadAccount
|
||||
|
@ -237,8 +249,6 @@ public class HomeTimelineService {
|
|||
|
||||
self.copy(from: status, to: statusData, on: backgroundContext)
|
||||
}
|
||||
|
||||
return statusesWithImages
|
||||
}
|
||||
|
||||
private func copy(from status: Status,
|
||||
|
@ -287,4 +297,9 @@ public class HomeTimelineService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func prefetch(statuses: [Status]) {
|
||||
let statusModels = statuses.getStatusesWithImagesOnly().toStatusModels()
|
||||
imagePrefetcher.startPrefetching(with: statusModels.getAllImagesUrls())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import Nuke
|
||||
import PixelfedKit
|
||||
import ClientKit
|
||||
import ServicesKit
|
||||
|
@ -38,6 +39,7 @@ struct PaginableStatusesView: View {
|
|||
@State private var page = 1
|
||||
|
||||
private let defaultLimit = 10
|
||||
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
|
@ -122,6 +124,9 @@ struct PaginableStatusesView: View {
|
|||
inPlaceStatuses.append(StatusModel(status: item))
|
||||
}
|
||||
|
||||
// Prefetch images.
|
||||
self.prefetch(statusModels: inPlaceStatuses)
|
||||
|
||||
self.statusViewModels.append(contentsOf: inPlaceStatuses)
|
||||
}
|
||||
|
||||
|
@ -140,6 +145,9 @@ struct PaginableStatusesView: View {
|
|||
inPlaceStatuses.append(StatusModel(status: item))
|
||||
}
|
||||
|
||||
// Prefetch images.
|
||||
self.prefetch(statusModels: inPlaceStatuses)
|
||||
|
||||
self.statusViewModels.append(contentsOf: inPlaceStatuses)
|
||||
}
|
||||
|
||||
|
@ -152,4 +160,8 @@ struct PaginableStatusesView: View {
|
|||
return try await self.client.accounts?.bookmarks(limit: self.defaultLimit, page: self.page) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
private func prefetch(statusModels: [StatusModel]) {
|
||||
imagePrefetcher.startPrefetching(with: statusModels.getAllImagesUrls())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import Nuke
|
||||
import PixelfedKit
|
||||
import ClientKit
|
||||
import ServicesKit
|
||||
|
@ -50,6 +51,7 @@ struct StatusesView: View {
|
|||
@State private var lastStatusId: String?
|
||||
|
||||
private let defaultLimit = 20
|
||||
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
|
@ -150,6 +152,9 @@ struct StatusesView: View {
|
|||
inPlaceStatuses.append(StatusModel(status: item))
|
||||
}
|
||||
|
||||
// Prefetch images.
|
||||
self.prefetch(statusModels: inPlaceStatuses)
|
||||
|
||||
// Append to empty list.
|
||||
self.statusViewModels.append(contentsOf: inPlaceStatuses)
|
||||
}
|
||||
|
@ -174,6 +179,9 @@ struct StatusesView: View {
|
|||
inPlaceStatuses.append(StatusModel(status: item))
|
||||
}
|
||||
|
||||
// Prefetch images.
|
||||
self.prefetch(statusModels: inPlaceStatuses)
|
||||
|
||||
// Append statuses to existing array of statuses (at the end).
|
||||
self.statusViewModels.append(contentsOf: inPlaceStatuses)
|
||||
}
|
||||
|
@ -291,4 +299,8 @@ struct StatusesView: View {
|
|||
ErrorService.shared.handle(error, message: "statuses.error.tagUnfollowFailed", showToastr: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func prefetch(statusModels: [StatusModel]) {
|
||||
imagePrefetcher.startPrefetching(with: statusModels.getAllImagesUrls())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ struct ImageRowItem: View {
|
|||
@State private var showThumbImage = false
|
||||
@State private var cancelled = true
|
||||
@State private var error: Error?
|
||||
@State private var opacity = 0.0
|
||||
@State private var isFavourited = false
|
||||
|
||||
private let onImageDownloaded: (Double, Double) -> Void
|
||||
|
@ -58,21 +57,9 @@ struct ImageRowItem: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.imageContainerView(uiImage: uiImage)
|
||||
.imageContextMenu(statusData: self.status, attachmentData: self.attachmentData, uiImage: uiImage)
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cancelled {
|
||||
|
|
|
@ -22,7 +22,6 @@ struct ImageRowItemAsync: View {
|
|||
private let showAvatar: Bool
|
||||
|
||||
@State private var showThumbImage = false
|
||||
@State private var opacity = 0.0
|
||||
@State private var isFavourited = false
|
||||
|
||||
private let onImageDownloaded: (Double, Double) -> Void
|
||||
|
@ -61,30 +60,20 @@ struct ImageRowItemAsync: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
if let uiImage = state.imageResponse?.image {
|
||||
self.recalculateSizeOfDownloadedImage(uiImage: uiImage)
|
||||
}
|
||||
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.imageContainerView(image: image)
|
||||
.imageContextMenu(statusModel: self.statusViewModel,
|
||||
attachmentModel: self.attachment,
|
||||
uiImage: state.imageResponse?.image)
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
if let uiImage = state.imageResponse?.image {
|
||||
self.recalculateSizeOfDownloadedImage(uiImage: uiImage)
|
||||
}
|
||||
|
||||
withAnimation {
|
||||
self.opacity = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if state.error != nil {
|
||||
|
|
Loading…
Reference in New Issue