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()
|
|
|
|
}
|
|
|
|
}
|