From 5d893a4b428fa821775c23a679bfdcbf51bb2968 Mon Sep 17 00:00:00 2001 From: Marcin Czachursk Date: Mon, 2 Jan 2023 08:11:38 +0100 Subject: [PATCH] Add loading in background --- .../CoreData/AttachmentDataHandler.swift | 5 +- Vernissage/CoreData/CoreDataHandler.swift | 4 + Vernissage/CoreData/StatusDataHandler.swift | 24 +++- Vernissage/Views/HomeFeedView.swift | 106 ++++++++++++++---- 4 files changed, 111 insertions(+), 28 deletions(-) diff --git a/Vernissage/CoreData/AttachmentDataHandler.swift b/Vernissage/CoreData/AttachmentDataHandler.swift index 8832d68..3b75033 100644 --- a/Vernissage/CoreData/AttachmentDataHandler.swift +++ b/Vernissage/CoreData/AttachmentDataHandler.swift @@ -6,6 +6,7 @@ import Foundation +import CoreData class AttachmentDataHandler { func getAttachmentsData() -> [AttachmentData] { @@ -19,8 +20,8 @@ class AttachmentDataHandler { } } - func createAttachmnentDataEntity() -> AttachmentData { - let context = CoreDataHandler.shared.container.viewContext + func createAttachmnentDataEntity(viewContext: NSManagedObjectContext? = nil) -> AttachmentData { + let context = viewContext ?? CoreDataHandler.shared.container.viewContext return AttachmentData(context: context) } } diff --git a/Vernissage/CoreData/CoreDataHandler.swift b/Vernissage/CoreData/CoreDataHandler.swift index 9fb782a..b1528d8 100644 --- a/Vernissage/CoreData/CoreDataHandler.swift +++ b/Vernissage/CoreData/CoreDataHandler.swift @@ -35,6 +35,10 @@ public class CoreDataHandler { container.viewContext.automaticallyMergesChangesFromParent = true } + public func newBackgroundContext() -> NSManagedObjectContext { + self.container.newBackgroundContext() + } + public func save() { let context = self.container.viewContext if context.hasChanges { diff --git a/Vernissage/CoreData/StatusDataHandler.swift b/Vernissage/CoreData/StatusDataHandler.swift index 3c7b1c8..b7f5934 100644 --- a/Vernissage/CoreData/StatusDataHandler.swift +++ b/Vernissage/CoreData/StatusDataHandler.swift @@ -6,6 +6,7 @@ import Foundation +import CoreData class StatusDataHandler { func getStatusesData() -> [StatusData] { @@ -19,8 +20,8 @@ class StatusDataHandler { } } - func getMaximumStatus() -> StatusData? { - let context = CoreDataHandler.shared.container.viewContext + func getMaximumStatus(viewContext: NSManagedObjectContext? = nil) -> StatusData? { + let context = viewContext ?? CoreDataHandler.shared.container.viewContext let fetchRequest = StatusData.fetchRequest() fetchRequest.fetchLimit = 1 @@ -34,8 +35,23 @@ class StatusDataHandler { } } - func createStatusDataEntity() -> StatusData { - let context = CoreDataHandler.shared.container.viewContext + func getMinimumtatus(viewContext: NSManagedObjectContext? = nil) -> StatusData? { + let context = viewContext ?? CoreDataHandler.shared.container.viewContext + let fetchRequest = StatusData.fetchRequest() + + fetchRequest.fetchLimit = 1 + let sortDescriptor = NSSortDescriptor(key: "id", ascending: true) + fetchRequest.sortDescriptors = [sortDescriptor] + do { + let statuses = try context.fetch(fetchRequest) + return statuses.first + } catch { + return nil + } + } + + func createStatusDataEntity(viewContext: NSManagedObjectContext? = nil) -> StatusData { + let context = viewContext ?? CoreDataHandler.shared.container.viewContext return StatusData(context: context) } } diff --git a/Vernissage/Views/HomeFeedView.swift b/Vernissage/Views/HomeFeedView.swift index a7b8be7..5b0f075 100644 --- a/Vernissage/Views/HomeFeedView.swift +++ b/Vernissage/Views/HomeFeedView.swift @@ -8,6 +8,7 @@ import SwiftUI import MastodonSwift import UIKit +import CoreData struct HomeFeedView: View { @Environment(\.managedObjectContext) private var viewContext @@ -26,12 +27,44 @@ struct HomeFeedView: View { LazyVGrid(columns: gridColumns) { ForEach(dbStatuses) { item in NavigationLink(destination: DetailsView(statusData: item)) { - if let attachmenData = item.attachmentRelation?.first(where: { element in true }) as? AttachmentData { - Image(uiImage: UIImage(data: attachmenData.data)!) - .resizable().aspectRatio(contentMode: .fit) + if let attachmenData = item.attachmentRelation?.first(where: { element in true }) as? AttachmentData, + let uiImage = UIImage(data: attachmenData.data) { + + ZStack { + Image(uiImage: uiImage) + .resizable().aspectRatio(contentMode: .fit) + if let count = item.attachmentRelation?.count, count > 1 { + VStack(alignment:.trailing) { + Spacer() + HStack { + Spacer() + Text("1 / \(count)") + .padding(.horizontal, 6) + .padding(.vertical, 3) + .font(.caption2) + .foregroundColor(.black) + .background(.ultraThinMaterial, in: Capsule()) + } + }.padding() + } + } + } else { + Text("Error") } } } + + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .onAppear { + Task { + do { + try await onBottomOfList() + } catch { + print("Error", error) + } + } + } } } @@ -58,7 +91,7 @@ struct HomeFeedView: View { } .refreshable { do { - try await loadData() + try await onTopOfList() } catch { print("Error", error) } @@ -67,7 +100,7 @@ struct HomeFeedView: View { do { if self.dbStatuses.isEmpty { self.showLoading = true - try await loadData() + try await onTopOfList() self.showLoading = false } } catch { @@ -77,25 +110,54 @@ struct HomeFeedView: View { } } - private func loadData() async throws { + private func onBottomOfList() async throws { + // Load data from API and operate on CoreData on background context. + let backgroundContext = CoreDataHandler.shared.newBackgroundContext() + + // Get maximimum downloaded stauts id. + let statusDataHandler = StatusDataHandler() + let oldestStatus = statusDataHandler.getMinimumtatus(viewContext: backgroundContext) + + guard let oldestStatus = oldestStatus else { + return + } + + try await self.loadData(on: backgroundContext, maxId: oldestStatus.id) + } + + private func onTopOfList() async throws { + // Load data from API and operate on CoreData on background context. + let backgroundContext = CoreDataHandler.shared.newBackgroundContext() + + // Get maximimum downloaded stauts id. + let statusDataHandler = StatusDataHandler() + let newestStatus = statusDataHandler.getMaximumStatus(viewContext: backgroundContext) + + guard let newestStatus = newestStatus else { + return + } + + try await self.loadData(on: backgroundContext, minId: newestStatus.id) + } + + private func loadData(on backgroundContext: NSManagedObjectContext, minId: String? = nil, maxId: String? = nil) async throws { guard let accessData = self.applicationState.accountData, let accessToken = accessData.accessToken else { return } - + // Get maximimum downloaded stauts id. let attachmentDataHandler = AttachmentDataHandler() let statusDataHandler = StatusDataHandler() - let lastStatus = statusDataHandler.getMaximumStatus() // Retrieve statuses from API. let client = MastodonClient(baseURL: accessData.serverUrl).getAuthenticated(token: accessToken) - let statuses = try await client.getHomeTimeline(minId: lastStatus?.id, limit: 40) + let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 40) // Download status images and save it into database. for status in statuses { // Save status data in database. - let statusDataEntity = statusDataHandler.createStatusDataEntity() + let statusDataEntity = statusDataHandler.createStatusDataEntity(viewContext: backgroundContext) statusDataEntity.accountAvatar = status.account?.avatar statusDataEntity.accountDisplayName = status.account?.displayName statusDataEntity.accountId = status.account!.id @@ -119,7 +181,7 @@ struct HomeFeedView: View { statusDataEntity.uri = status.uri statusDataEntity.url = status.url statusDataEntity.visibility = status.visibility.rawValue - + for attachment in status.mediaAttachments { let imageData = try await self.fetchImage(attachment: attachment) @@ -128,17 +190,17 @@ struct HomeFeedView: View { } /* - var exif = image.getExifData() - if let dict = exif as? [String: AnyObject] { - dict.keys.map { key in - print(key) - print(dict[key]) - } - } - */ + var exif = image.getExifData() + if let dict = exif as? [String: AnyObject] { + dict.keys.map { key in + print(key) + print(dict[key]) + } + } + */ // Save attachment in database. - let attachmentData = attachmentDataHandler.createAttachmnentDataEntity() + let attachmentData = attachmentDataHandler.createAttachmnentDataEntity(viewContext: backgroundContext) attachmentData.id = attachment.id attachmentData.url = attachment.url attachmentData.blurhash = attachment.blurhash @@ -149,13 +211,13 @@ struct HomeFeedView: View { attachmentData.statusId = statusDataEntity.id attachmentData.data = imageData - + attachmentData.statusRelation = statusDataEntity statusDataEntity.addToAttachmentRelation(attachmentData) } } - try self.viewContext.save() + try backgroundContext.save() } public func fetchImage(attachment: Attachment) async throws -> Data? {