Impressia/Vernissage/Views/HomeFeedView.swift

222 lines
9.0 KiB
Swift
Raw Normal View History

2022-12-30 18:20:54 +01:00
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
import MastodonSwift
import UIKit
2023-01-02 08:11:38 +01:00
import CoreData
2022-12-30 18:20:54 +01:00
struct HomeFeedView: View {
2023-01-01 18:13:36 +01:00
@Environment(\.managedObjectContext) private var viewContext
2022-12-31 16:31:05 +01:00
@EnvironmentObject var applicationState: ApplicationState
2022-12-30 18:20:54 +01:00
@State private var showLoading = false
private static let initialColumns = 1
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
2023-01-01 18:13:36 +01:00
@FetchRequest(sortDescriptors: [SortDescriptor(\.id, order: .reverse)]) var dbStatuses: FetchedResults<StatusData>
2022-12-30 18:20:54 +01:00
var body: some View {
ZStack {
ScrollView {
LazyVGrid(columns: gridColumns) {
2023-01-02 19:12:25 +01:00
ForEach(dbStatuses, id: \.self) { item in
2023-01-01 18:13:36 +01:00
NavigationLink(destination: DetailsView(statusData: item)) {
2023-01-02 19:12:25 +01:00
if let attachmenData = item.attachmentRelation?.first,
2023-01-02 08:11:38 +01:00
let uiImage = UIImage(data: attachmenData.data) {
ZStack {
Image(uiImage: uiImage)
2023-01-02 19:12:25 +01:00
.resizable()
.aspectRatio(contentMode: .fit)
2023-01-02 08:11:38 +01:00
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")
2023-01-01 18:13:36 +01:00
}
2022-12-30 18:20:54 +01:00
}
}
2023-01-02 08:11:38 +01:00
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.onAppear {
Task {
do {
try await onBottomOfList()
} catch {
print("Error", error)
}
}
}
2022-12-30 18:20:54 +01:00
}
}
if showLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
}
.refreshable {
do {
2023-01-02 08:11:38 +01:00
try await onTopOfList()
2022-12-30 18:20:54 +01:00
} catch {
print("Error", error)
}
}
.task {
do {
2023-01-01 18:13:36 +01:00
if self.dbStatuses.isEmpty {
self.showLoading = true
2023-01-02 08:11:38 +01:00
try await onTopOfList()
2023-01-01 18:13:36 +01:00
self.showLoading = false
}
2022-12-30 18:20:54 +01:00
} catch {
2022-12-31 16:31:05 +01:00
self.showLoading = false
2022-12-30 18:20:54 +01:00
print("Error", error)
}
}
}
2023-01-02 08:11:38 +01:00
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 {
2022-12-31 16:31:05 +01:00
guard let accessData = self.applicationState.accountData, let accessToken = accessData.accessToken else {
return
}
2023-01-02 08:11:38 +01:00
2023-01-01 18:13:36 +01:00
// Get maximimum downloaded stauts id.
let attachmentDataHandler = AttachmentDataHandler()
let statusDataHandler = StatusDataHandler()
// Retrieve statuses from API.
2022-12-31 16:31:05 +01:00
let client = MastodonClient(baseURL: accessData.serverUrl).getAuthenticated(token: accessToken)
2023-01-02 08:11:38 +01:00
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 40)
2022-12-30 18:20:54 +01:00
2023-01-01 18:13:36 +01:00
// Download status images and save it into database.
for status in statuses {
// Save status data in database.
2023-01-02 08:11:38 +01:00
let statusDataEntity = statusDataHandler.createStatusDataEntity(viewContext: backgroundContext)
2023-01-01 18:13:36 +01:00
statusDataEntity.accountAvatar = status.account?.avatar
statusDataEntity.accountDisplayName = status.account?.displayName
statusDataEntity.accountId = status.account!.id
statusDataEntity.accountUsername = status.account!.username
statusDataEntity.applicationName = status.application?.name
statusDataEntity.applicationWebsite = status.application?.website
statusDataEntity.bookmarked = status.bookmarked
statusDataEntity.content = status.content
statusDataEntity.createdAt = status.createdAt
statusDataEntity.favourited = status.favourited
statusDataEntity.favouritesCount = Int32(status.favouritesCount)
statusDataEntity.id = status.id
statusDataEntity.inReplyToAccount = status.inReplyToAccount
statusDataEntity.inReplyToId = status.inReplyToId
statusDataEntity.muted = status.muted
statusDataEntity.pinned = status.pinned
statusDataEntity.reblogged = status.reblogged
statusDataEntity.reblogsCount = Int32(status.reblogsCount)
statusDataEntity.sensitive = status.sensitive
statusDataEntity.spoilerText = status.spoilerText
statusDataEntity.uri = status.uri
statusDataEntity.url = status.url
statusDataEntity.visibility = status.visibility.rawValue
2023-01-02 08:11:38 +01:00
2023-01-01 18:13:36 +01:00
for attachment in status.mediaAttachments {
let imageData = try await self.fetchImage(attachment: attachment)
guard let imageData = imageData else {
continue
}
/*
2023-01-02 08:11:38 +01:00
var exif = image.getExifData()
if let dict = exif as? [String: AnyObject] {
dict.keys.map { key in
print(key)
print(dict[key])
}
}
*/
2023-01-01 18:13:36 +01:00
// Save attachment in database.
2023-01-02 08:11:38 +01:00
let attachmentData = attachmentDataHandler.createAttachmnentDataEntity(viewContext: backgroundContext)
2023-01-01 18:13:36 +01:00
attachmentData.id = attachment.id
attachmentData.url = attachment.url
attachmentData.blurhash = attachment.blurhash
attachmentData.previewUrl = attachment.previewUrl
attachmentData.remoteUrl = attachment.remoteUrl
attachmentData.text = attachment.description
attachmentData.type = attachment.type.rawValue
attachmentData.statusId = statusDataEntity.id
attachmentData.data = imageData
2023-01-02 08:11:38 +01:00
2023-01-01 18:13:36 +01:00
attachmentData.statusRelation = statusDataEntity
statusDataEntity.addToAttachmentRelation(attachmentData)
2022-12-30 18:20:54 +01:00
}
}
2023-01-02 08:11:38 +01:00
try backgroundContext.save()
2022-12-30 18:20:54 +01:00
}
2023-01-01 18:13:36 +01:00
public func fetchImage(attachment: Attachment) async throws -> Data? {
guard let data = try await RemoteFileService.shared.fetchData(url: attachment.url) else {
2022-12-30 18:20:54 +01:00
return nil
}
2023-01-01 18:13:36 +01:00
return data
2022-12-30 18:20:54 +01:00
}
}
struct HomeFeedView_Previews: PreviewProvider {
static var previews: some View {
HomeFeedView()
}
}