Store statuses in database

This commit is contained in:
Marcin Czachursk 2023-01-01 18:13:36 +01:00
parent 37c6dc0699
commit 0d493a20a3
13 changed files with 459 additions and 117 deletions

View File

@ -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 */,

View File

@ -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

View File

@ -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 {
}

View File

@ -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 {
}

View File

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

View File

@ -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 {
}

View File

@ -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 {
}

View File

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

View File

@ -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>

View File

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

View File

@ -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)

View File

@ -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
}
}

View File

@ -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 }
}
}