Store statuses in database
This commit is contained in:
parent
37c6dc0699
commit
0d493a20a3
|
@ -7,6 +7,12 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */; };
|
||||
F80048042961850500E6868A /* AttachmentData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */; };
|
||||
F80048052961850500E6868A /* StatusData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048012961850500E6868A /* StatusData+CoreDataClass.swift */; };
|
||||
F80048062961850500E6868A /* StatusData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */; };
|
||||
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048072961E6DE00E6868A /* StatusDataHandler.swift */; };
|
||||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048092961EA1900E6868A /* AttachmentDataHandler.swift */; };
|
||||
F8341F90295C636C009C8EE6 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* UIImage.swift */; };
|
||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F91295C63BB009C8EE6 /* ImageStatus.swift */; };
|
||||
F83901A4295D864D00456AE2 /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83901A3295D864D00456AE2 /* TagView.swift */; };
|
||||
|
@ -38,6 +44,12 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
F80048012961850500E6868A /* StatusData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
F80048072961E6DE00E6868A /* StatusDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusDataHandler.swift; sourceTree = "<group>"; };
|
||||
F80048092961EA1900E6868A /* AttachmentDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentDataHandler.swift; sourceTree = "<group>"; };
|
||||
F8341F8F295C636C009C8EE6 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
|
||||
F8341F91295C63BB009C8EE6 /* ImageStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStatus.swift; sourceTree = "<group>"; };
|
||||
F83901A3295D864D00456AE2 /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = "<group>"; };
|
||||
|
@ -116,6 +128,10 @@
|
|||
F8341F96295C6427009C8EE6 /* CoreData */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */,
|
||||
F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */,
|
||||
F80048012961850500E6868A /* StatusData+CoreDataClass.swift */,
|
||||
F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */,
|
||||
F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */,
|
||||
F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */,
|
||||
F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */,
|
||||
|
@ -123,6 +139,8 @@
|
|||
F88C2474295C37BB0006098B /* CoreDataHandler.swift */,
|
||||
F866F6A229604161002E8F88 /* AccountDataHandler.swift */,
|
||||
F866F6A429604194002E8F88 /* ApplicationSettingsHandler.swift */,
|
||||
F80048072961E6DE00E6868A /* StatusDataHandler.swift */,
|
||||
F80048092961EA1900E6868A /* AttachmentDataHandler.swift */,
|
||||
);
|
||||
path = CoreData;
|
||||
sourceTree = "<group>";
|
||||
|
@ -272,14 +290,20 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
|
||||
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
||||
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
||||
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
|
||||
F80048062961850500E6868A /* StatusData+CoreDataProperties.swift in Sources */,
|
||||
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */,
|
||||
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */,
|
||||
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
|
||||
F80048052961850500E6868A /* StatusData+CoreDataClass.swift in Sources */,
|
||||
F80048042961850500E6868A /* AttachmentData+CoreDataProperties.swift in Sources */,
|
||||
F83901A6295D8EC000456AE2 /* LabelIconView.swift in Sources */,
|
||||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */,
|
||||
F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */,
|
||||
F8341F90295C636C009C8EE6 /* UIImage.swift in Sources */,
|
||||
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
||||
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
|
||||
|
|
|
@ -18,6 +18,23 @@ class AccountDataHandler {
|
|||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func getCurrentAccountData() -> AccountData? {
|
||||
let accounts = self.getAccountsData()
|
||||
|
||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
||||
|
||||
let currentAccount = accounts.first { accountData in
|
||||
accountData.id == defaultSettings.currentAccount
|
||||
}
|
||||
|
||||
if let currentAccount {
|
||||
return currentAccount
|
||||
}
|
||||
|
||||
return accounts.first
|
||||
}
|
||||
|
||||
func createAccountDataEntity() -> AccountData {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@objc(AttachmentData)
|
||||
public class AttachmentData: NSManagedObject {
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension AttachmentData {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<AttachmentData> {
|
||||
return NSFetchRequest<AttachmentData>(entityName: "AttachmentData")
|
||||
}
|
||||
|
||||
@NSManaged public var blurhash: String?
|
||||
@NSManaged public var data: Data
|
||||
@NSManaged public var id: String
|
||||
@NSManaged public var previewUrl: URL?
|
||||
@NSManaged public var remoteUrl: URL?
|
||||
@NSManaged public var statusId: String
|
||||
@NSManaged public var text: String?
|
||||
@NSManaged public var type: String
|
||||
@NSManaged public var url: URL
|
||||
@NSManaged public var statusRelation: StatusData?
|
||||
|
||||
}
|
||||
|
||||
extension AttachmentData : Identifiable {
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
class AttachmentDataHandler {
|
||||
func getAttachmentsData() -> [AttachmentData] {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = AttachmentData.fetchRequest()
|
||||
do {
|
||||
return try context.fetch(fetchRequest)
|
||||
} catch {
|
||||
print("Error during fetching accounts")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func createAttachmnentDataEntity() -> AttachmentData {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
return AttachmentData(context: context)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@objc(StatusData)
|
||||
public class StatusData: NSManagedObject {
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension StatusData {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<StatusData> {
|
||||
return NSFetchRequest<StatusData>(entityName: "StatusData")
|
||||
}
|
||||
|
||||
@NSManaged public var accountAvatar: URL?
|
||||
@NSManaged public var accountDisplayName: String?
|
||||
@NSManaged public var accountId: String
|
||||
@NSManaged public var accountUsername: String
|
||||
@NSManaged public var applicationName: String?
|
||||
@NSManaged public var applicationWebsite: URL?
|
||||
@NSManaged public var bookmarked: Bool
|
||||
@NSManaged public var content: String
|
||||
@NSManaged public var createdAt: String
|
||||
@NSManaged public var favourited: Bool
|
||||
@NSManaged public var favouritesCount: Int32
|
||||
@NSManaged public var id: String
|
||||
@NSManaged public var inReplyToAccount: String?
|
||||
@NSManaged public var inReplyToId: String?
|
||||
@NSManaged public var muted: Bool
|
||||
@NSManaged public var pinned: Bool
|
||||
@NSManaged public var reblogged: Bool
|
||||
@NSManaged public var reblogsCount: Int32
|
||||
@NSManaged public var sensitive: Bool
|
||||
@NSManaged public var spoilerText: String?
|
||||
@NSManaged public var uri: String?
|
||||
@NSManaged public var url: URL?
|
||||
@NSManaged public var visibility: String
|
||||
@NSManaged public var attachmentRelation: NSSet?
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for attachmentRelation
|
||||
extension StatusData {
|
||||
|
||||
@objc(addAttachmentRelationObject:)
|
||||
@NSManaged public func addToAttachmentRelation(_ value: AttachmentData)
|
||||
|
||||
@objc(removeAttachmentRelationObject:)
|
||||
@NSManaged public func removeFromAttachmentRelation(_ value: AttachmentData)
|
||||
|
||||
@objc(addAttachmentRelation:)
|
||||
@NSManaged public func addToAttachmentRelation(_ values: NSSet)
|
||||
|
||||
@objc(removeAttachmentRelation:)
|
||||
@NSManaged public func removeFromAttachmentRelation(_ values: NSSet)
|
||||
|
||||
}
|
||||
|
||||
extension StatusData : Identifiable {
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
class StatusDataHandler {
|
||||
func getStatusesData() -> [StatusData] {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = StatusData.fetchRequest()
|
||||
do {
|
||||
return try context.fetch(fetchRequest)
|
||||
} catch {
|
||||
print("Error during fetching accounts")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func getMaximumStatus() -> StatusData? {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = StatusData.fetchRequest()
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
let sortDescriptor = NSSortDescriptor(key: "id", ascending: false)
|
||||
fetchRequest.sortDescriptors = [sortDescriptor]
|
||||
do {
|
||||
let statuses = try context.fetch(fetchRequest)
|
||||
return statuses.first
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createStatusDataEntity() -> StatusData {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
return StatusData(context: context)
|
||||
}
|
||||
}
|
|
@ -24,4 +24,42 @@
|
|||
<entity name="ApplicationSettings" representedClassName="ApplicationSettings" syncable="YES">
|
||||
<attribute name="currentAccount" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="AttachmentData" representedClassName="AttachmentData" syncable="YES">
|
||||
<attribute name="blurhash" optional="YES" attributeType="String"/>
|
||||
<attribute name="data" attributeType="Binary"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="previewUrl" optional="YES" attributeType="URI"/>
|
||||
<attribute name="remoteUrl" optional="YES" attributeType="URI"/>
|
||||
<attribute name="statusId" attributeType="String"/>
|
||||
<attribute name="text" optional="YES" attributeType="String"/>
|
||||
<attribute name="type" attributeType="String"/>
|
||||
<attribute name="url" attributeType="URI"/>
|
||||
<relationship name="statusRelation" maxCount="1" deletionRule="Cascade" destinationEntity="StatusData" inverseName="attachmentRelation" inverseEntity="StatusData"/>
|
||||
</entity>
|
||||
<entity name="StatusData" representedClassName="StatusData" syncable="YES">
|
||||
<attribute name="accountAvatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="accountDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="accountId" attributeType="String"/>
|
||||
<attribute name="accountUsername" optional="YES" attributeType="String"/>
|
||||
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
||||
<attribute name="applicationWebsite" optional="YES" attributeType="URI"/>
|
||||
<attribute name="bookmarked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="content" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="String"/>
|
||||
<attribute name="favourited" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="favouritesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="inReplyToAccount" optional="YES" attributeType="String"/>
|
||||
<attribute name="inReplyToId" optional="YES" attributeType="String"/>
|
||||
<attribute name="muted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="pinned" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="reblogged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="reblogsCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
||||
<attribute name="uri" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||
<attribute name="visibility" attributeType="String"/>
|
||||
<relationship name="attachmentRelation" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AttachmentData" inverseName="statusRelation" inverseEntity="AttachmentData"/>
|
||||
</entity>
|
||||
</model>
|
|
@ -36,16 +36,17 @@ struct VernissageApp: SwiftUI.App {
|
|||
}
|
||||
.task {
|
||||
let accountDataHandler = AccountDataHandler()
|
||||
let accounts = accountDataHandler.getAccountsData()
|
||||
let currentAccount = accountDataHandler.getCurrentAccountData()
|
||||
|
||||
// When we dont have even one account stored in database then we have to ask user to enter server and sign in.
|
||||
guard let accountData = accounts.first, let accessToken = accountData.accessToken else {
|
||||
guard let accountData = currentAccount, let accessToken = accountData.accessToken else {
|
||||
self.applicationViewMode = .signIn
|
||||
return
|
||||
}
|
||||
|
||||
// When we have at least one account then we have to verify access token.
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||
|
||||
do {
|
||||
let account = try await client.verifyCredentials()
|
||||
try await self.updateAccount(accountData: accountData, account: account)
|
||||
|
@ -117,6 +118,11 @@ struct VernissageApp: SwiftUI.App {
|
|||
}
|
||||
}
|
||||
|
||||
// We have to be sure that account id is saved as default account.
|
||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
||||
defaultSettings.currentAccount = accountData.id
|
||||
|
||||
// Save account data in database and in application state.
|
||||
try self.coreDataHandler.container.viewContext.save()
|
||||
}
|
||||
|
|
|
@ -9,18 +9,20 @@ import MastodonSwift
|
|||
|
||||
struct DetailsView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State public var current: ImageStatus
|
||||
@State public var statusData: StatusData
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack (alignment: .leading) {
|
||||
Image(uiImage: current.image)
|
||||
.resizable().aspectRatio(contentMode: .fit)
|
||||
.frame(maxWidth: .infinity)
|
||||
if let attachmentData = statusData.attachmentRelation?.first(where: { elemet in true}) as? AttachmentData {
|
||||
Image(uiImage: UIImage(data: attachmentData.data)!)
|
||||
.resizable().aspectRatio(contentMode: .fit)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack (alignment: .center) {
|
||||
AsyncImage(url: current.status.account?.avatar) { image in
|
||||
AsyncImage(url: statusData.accountAvatar) { image in
|
||||
image
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
|
@ -33,16 +35,16 @@ struct DetailsView: View {
|
|||
.frame(width: 48.0, height: 48.0)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text(current.status.account?.displayName ?? current.status.account?.username ?? "")
|
||||
Text(statusData.accountDisplayName ?? statusData.accountUsername)
|
||||
.foregroundColor(Color("displayNameColor"))
|
||||
Text("@\(current.status.account?.username ?? "unknown")")
|
||||
Text("@\(statusData.accountUsername)")
|
||||
.foregroundColor(Color("lightGrayColor"))
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
}
|
||||
|
||||
HTMLFormattedText(current.status.content)
|
||||
HTMLFormattedText(statusData.content)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
LabelIconView(iconName: "camera", value: "SONY ILCE-7M3")
|
||||
|
@ -57,8 +59,8 @@ struct DetailsView: View {
|
|||
// Favorite
|
||||
} content: {
|
||||
HStack {
|
||||
Image(systemName: current.status.favourited ? "heart.fill" : "heart")
|
||||
Text("\(current.status.favouritesCount) likes")
|
||||
Image(systemName: statusData.favourited ? "heart.fill" : "heart")
|
||||
Text("\(statusData.favouritesCount) likes")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,8 +68,8 @@ struct DetailsView: View {
|
|||
// Reboost
|
||||
} content: {
|
||||
HStack {
|
||||
Image(systemName: current.status.reblogged ? "arrowshape.turn.up.forward.fill" : "arrowshape.turn.up.forward")
|
||||
Text("\(current.status.reblogsCount) boosts")
|
||||
Image(systemName: statusData.reblogged ? "arrowshape.turn.up.forward.fill" : "arrowshape.turn.up.forward")
|
||||
Text("\(statusData.reblogsCount) boosts")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +78,7 @@ struct DetailsView: View {
|
|||
TagView {
|
||||
// Bookmark
|
||||
} content: {
|
||||
Image(systemName: current.status.bookmarked ? "bookmark.fill" : "bookmark")
|
||||
Image(systemName: statusData.bookmarked ? "bookmark.fill" : "bookmark")
|
||||
}
|
||||
}
|
||||
.font(.subheadline)
|
||||
|
|
|
@ -10,23 +10,26 @@ import MastodonSwift
|
|||
import UIKit
|
||||
|
||||
struct HomeFeedView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
||||
@State private var statuses: [Status] = []
|
||||
@State private var images: [ImageStatus] = []
|
||||
@State private var showLoading = false
|
||||
|
||||
private static let initialColumns = 1
|
||||
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
|
||||
|
||||
@FetchRequest(sortDescriptors: [SortDescriptor(\.id, order: .reverse)]) var dbStatuses: FetchedResults<StatusData>
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: gridColumns) {
|
||||
ForEach(images) { item in
|
||||
NavigationLink(destination: DetailsView(current: item)) {
|
||||
Image(uiImage: item.image)
|
||||
.resizable().aspectRatio(contentMode: .fit)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,9 +65,11 @@ struct HomeFeedView: View {
|
|||
}
|
||||
.task {
|
||||
do {
|
||||
self.showLoading = true
|
||||
try await loadData()
|
||||
self.showLoading = false
|
||||
if self.dbStatuses.isEmpty {
|
||||
self.showLoading = true
|
||||
try await loadData()
|
||||
self.showLoading = false
|
||||
}
|
||||
} catch {
|
||||
self.showLoading = false
|
||||
print("Error", error)
|
||||
|
@ -77,46 +82,88 @@ struct HomeFeedView: View {
|
|||
return
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: accessData.serverUrl).getAuthenticated(token: accessToken)
|
||||
self.statuses = try await client.getHomeTimeline(limit: 40)
|
||||
// Get maximimum downloaded stauts id.
|
||||
let attachmentDataHandler = AttachmentDataHandler()
|
||||
let statusDataHandler = StatusDataHandler()
|
||||
let lastStatus = statusDataHandler.getMaximumStatus()
|
||||
|
||||
var imagesCache: [ImageStatus] = []
|
||||
for item in self.statuses {
|
||||
let imageStatus = try await self.fetchImage(status: item)
|
||||
// Retrieve statuses from API.
|
||||
let client = MastodonClient(baseURL: accessData.serverUrl).getAuthenticated(token: accessToken)
|
||||
let statuses = try await client.getHomeTimeline(minId: lastStatus?.id, limit: 40)
|
||||
|
||||
// Download status images and save it into database.
|
||||
for status in statuses {
|
||||
|
||||
// Save status data in database.
|
||||
let statusDataEntity = statusDataHandler.createStatusDataEntity()
|
||||
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
|
||||
|
||||
for attachment in status.mediaAttachments {
|
||||
let imageData = try await self.fetchImage(attachment: attachment)
|
||||
|
||||
guard let imageData = imageData else {
|
||||
continue
|
||||
}
|
||||
|
||||
/*
|
||||
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()
|
||||
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
|
||||
|
||||
if let imageStatus {
|
||||
imagesCache.append(imageStatus)
|
||||
attachmentData.statusRelation = statusDataEntity
|
||||
statusDataEntity.addToAttachmentRelation(attachmentData)
|
||||
}
|
||||
}
|
||||
|
||||
self.images = imagesCache
|
||||
try self.viewContext.save()
|
||||
}
|
||||
|
||||
public func fetchImage(status: Status) async throws -> ImageStatus? {
|
||||
guard let url = status.mediaAttachments.first?.url, let id = status.mediaAttachments.first?.id else {
|
||||
public func fetchImage(attachment: Attachment) async throws -> Data? {
|
||||
guard let data = try await RemoteFileService.shared.fetchData(url: attachment.url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let data = try await RemoteFileService.shared.fetchData(url: url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let image = UIImage(data: data)
|
||||
guard let image = image else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
var exif = image.getExifData()
|
||||
if let dict = exif as? [String: AnyObject] {
|
||||
dict.keys.map { key in
|
||||
print(key)
|
||||
print(dict[key])
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return ImageStatus(id: id,image: image, status: status)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,67 +29,7 @@ struct SignInView: View {
|
|||
|
||||
Button("Go") {
|
||||
Task {
|
||||
let baseUrl = URL(string: serverAddress)!
|
||||
let client = MastodonClient(baseURL: baseUrl)
|
||||
|
||||
// Verify address.
|
||||
let instanceInformation = try await client.readInstanceInformation()
|
||||
print(instanceInformation)
|
||||
|
||||
// Create application (we will get clientId amd clientSecret).
|
||||
let oAuthApp = try await client.createApp(named: "Photofed",
|
||||
redirectUri: "oauth-vernissage://oauth-callback/mastodon",
|
||||
scopes: Scopes(["read", "write", "follow", "push"]),
|
||||
website: baseUrl)
|
||||
|
||||
// Authorize a user (browser, we will get clientCode).
|
||||
let oAuthSwiftCredential = try await client.authenticate(app: oAuthApp, scope: Scopes(["read", "write", "follow", "push"]))
|
||||
|
||||
// Get authenticated client.
|
||||
let authenticatedClient = client.getAuthenticated(token: oAuthSwiftCredential.oauthToken)
|
||||
|
||||
// Get account information from server.
|
||||
let account = try await authenticatedClient.verifyCredentials()
|
||||
|
||||
// Create account object in database.
|
||||
let accountDataHandler = AccountDataHandler()
|
||||
let accountData = accountDataHandler.createAccountDataEntity()
|
||||
|
||||
accountData.id = account.id
|
||||
accountData.username = account.username
|
||||
accountData.acct = account.acct
|
||||
accountData.displayName = account.displayName
|
||||
accountData.note = account.note
|
||||
accountData.url = account.url
|
||||
accountData.avatar = account.avatar
|
||||
accountData.header = account.header
|
||||
accountData.locked = account.locked
|
||||
accountData.createdAt = account.createdAt
|
||||
accountData.followersCount = Int32(account.followersCount)
|
||||
accountData.followingCount = Int32(account.followingCount)
|
||||
accountData.statusesCount = Int32(account.statusesCount)
|
||||
|
||||
accountData.serverUrl = baseUrl
|
||||
accountData.clientId = oAuthApp.clientId
|
||||
accountData.clientSecret = oAuthApp.clientSecret
|
||||
accountData.clientVapidKey = oAuthApp.vapidKey ?? ""
|
||||
accountData.accessToken = oAuthSwiftCredential.oauthToken
|
||||
|
||||
// Download avatar image.
|
||||
if let avatarUrl = account.avatar {
|
||||
do {
|
||||
let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl)
|
||||
accountData.avatarData = avatarData
|
||||
}
|
||||
catch {
|
||||
print("Avatar has not been downloaded")
|
||||
}
|
||||
}
|
||||
|
||||
// Save account data in database and in application state.
|
||||
try self.viewContext.save()
|
||||
self.applicationState.accountData = accountData
|
||||
self.onSignInStateChenge(.mainView)
|
||||
try await self.signIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,11 +38,83 @@ struct SignInView: View {
|
|||
.navigationBarTitle("Sign in to Pixelfed")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
private func signIn() async throws {
|
||||
let baseUrl = URL(string: serverAddress)!
|
||||
let client = MastodonClient(baseURL: baseUrl)
|
||||
|
||||
// Verify address.
|
||||
let instanceInformation = try await client.readInstanceInformation()
|
||||
print(instanceInformation)
|
||||
|
||||
// Create application (we will get clientId amd clientSecret).
|
||||
let oAuthApp = try await client.createApp(
|
||||
named: "Photofed",
|
||||
redirectUri: "oauth-vernissage://oauth-callback/mastodon",
|
||||
scopes: Scopes(["read", "write", "follow", "push"]),
|
||||
website: baseUrl)
|
||||
|
||||
// Authorize a user (browser, we will get clientCode).
|
||||
let oAuthSwiftCredential = try await client.authenticate(
|
||||
app: oAuthApp,
|
||||
scope: Scopes(["read", "write", "follow", "push"]))
|
||||
|
||||
// Get authenticated client.
|
||||
let authenticatedClient = client.getAuthenticated(token: oAuthSwiftCredential.oauthToken)
|
||||
|
||||
// Get account information from server.
|
||||
let account = try await authenticatedClient.verifyCredentials()
|
||||
|
||||
// Create account object in database.
|
||||
let accountDataHandler = AccountDataHandler()
|
||||
let accountData = accountDataHandler.createAccountDataEntity()
|
||||
|
||||
accountData.id = account.id
|
||||
accountData.username = account.username
|
||||
accountData.acct = account.acct
|
||||
accountData.displayName = account.displayName
|
||||
accountData.note = account.note
|
||||
accountData.url = account.url
|
||||
accountData.avatar = account.avatar
|
||||
accountData.header = account.header
|
||||
accountData.locked = account.locked
|
||||
accountData.createdAt = account.createdAt
|
||||
accountData.followersCount = Int32(account.followersCount)
|
||||
accountData.followingCount = Int32(account.followingCount)
|
||||
accountData.statusesCount = Int32(account.statusesCount)
|
||||
|
||||
accountData.serverUrl = baseUrl
|
||||
accountData.clientId = oAuthApp.clientId
|
||||
accountData.clientSecret = oAuthApp.clientSecret
|
||||
accountData.clientVapidKey = oAuthApp.vapidKey ?? ""
|
||||
accountData.accessToken = oAuthSwiftCredential.oauthToken
|
||||
|
||||
// Download avatar image.
|
||||
if let avatarUrl = account.avatar {
|
||||
do {
|
||||
let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl)
|
||||
accountData.avatarData = avatarData
|
||||
}
|
||||
catch {
|
||||
print("Avatar has not been downloaded")
|
||||
}
|
||||
}
|
||||
|
||||
// Set newly created account as current.
|
||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
||||
defaultSettings.currentAccount = accountData.id
|
||||
|
||||
// Save account data in database and in application state.
|
||||
try self.viewContext.save()
|
||||
|
||||
self.applicationState.accountData = accountData
|
||||
self.onSignInStateChenge(.mainView)
|
||||
}
|
||||
}
|
||||
|
||||
struct SignInView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SignInView { applicationViewMode in
|
||||
}
|
||||
SignInView { applicationViewMode in }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue