Storing account in CoreData.

This commit is contained in:
Marcin Czachursk 2022-12-30 18:20:54 +01:00
parent f140537825
commit ed8fa69d9a
14 changed files with 507 additions and 156 deletions

View File

@ -20,6 +20,15 @@
F88C2480295C38400006098B /* MastodonSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F88C247F295C38400006098B /* MastodonSwift */; };
F88C2482295C3A4F0006098B /* DetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2481295C3A4F0006098B /* DetailsView.swift */; };
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2485295C48030006098B /* HTMLFotmattedText.swift */; };
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD20295F3944009B20C9 /* HomeFeedView.swift */; };
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */; };
F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD24295F3FF7009B20C9 /* FederatedFeedView.swift */; };
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD26295F400E009B20C9 /* NotificationsView.swift */; };
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */; };
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */; };
F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */; };
F88FAD2F295F4D3C009B20C9 /* AccountData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD2E295F4D3C009B20C9 /* AccountData+CoreDataProperties.swift */; };
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD31295F5029009B20C9 /* RemoteFileService.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -36,6 +45,15 @@
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Vernissage.xcdatamodel; sourceTree = "<group>"; };
F88C2481295C3A4F0006098B /* DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsView.swift; sourceTree = "<group>"; };
F88C2485295C48030006098B /* HTMLFotmattedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLFotmattedText.swift; sourceTree = "<group>"; };
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeFeedView.swift; sourceTree = "<group>"; };
F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFeedView.swift; sourceTree = "<group>"; };
F88FAD24295F3FF7009B20C9 /* FederatedFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederatedFeedView.swift; sourceTree = "<group>"; };
F88FAD26295F400E009B20C9 /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountData+CoreDataClass.swift"; sourceTree = "<group>"; };
F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountData+CoreDataProperties.swift"; sourceTree = "<group>"; };
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationState.swift; sourceTree = "<group>"; };
F88FAD2E295F4D3C009B20C9 /* AccountData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "AccountData+CoreDataProperties.swift"; path = "Vernissage/CoreData/AccountData+CoreDataProperties.swift"; sourceTree = "<group>"; };
F88FAD31295F5029009B20C9 /* RemoteFileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteFileService.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -55,6 +73,10 @@
children = (
F88C2481295C3A4F0006098B /* DetailsView.swift */,
F88C246D295C37B80006098B /* MainView.swift */,
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */,
F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */,
F88FAD24295F3FF7009B20C9 /* FederatedFeedView.swift */,
F88FAD26295F400E009B20C9 /* NotificationsView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -71,6 +93,7 @@
isa = PBXGroup;
children = (
F8341F91295C63BB009C8EE6 /* ImageStatus.swift */,
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */,
);
path = Models;
sourceTree = "<group>";
@ -78,6 +101,8 @@
F8341F96295C6427009C8EE6 /* CoreData */ = {
isa = PBXGroup;
children = (
F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */,
F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */,
F88C2474295C37BB0006098B /* Persistence.swift */,
);
path = CoreData;
@ -103,6 +128,7 @@
F88C245F295C37B80006098B = {
isa = PBXGroup;
children = (
F88FAD2E295F4D3C009B20C9 /* AccountData+CoreDataProperties.swift */,
F88C246A295C37B80006098B /* Vernissage */,
F88C2469295C37B80006098B /* Products */,
);
@ -119,6 +145,7 @@
F88C246A295C37B80006098B /* Vernissage */ = {
isa = PBXGroup;
children = (
F88FAD30295F5010009B20C9 /* Services */,
F83901A2295D863B00456AE2 /* Widgets */,
F8341F97295C6434009C8EE6 /* Formatters */,
F8341F96295C6427009C8EE6 /* CoreData */,
@ -141,6 +168,14 @@
path = "Preview Content";
sourceTree = "<group>";
};
F88FAD30295F5010009B20C9 /* Services */ = {
isa = PBXGroup;
children = (
F88FAD31295F5029009B20C9 /* RemoteFileService.swift */,
);
path = Services;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -217,7 +252,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */,
F88FAD2F295F4D3C009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
F88C2475295C37BB0006098B /* Persistence.swift in Sources */,
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
F83901A6295D8EC000456AE2 /* LabelIconView.swift in Sources */,
F8341F90295C636C009C8EE6 /* UIImage.swift in Sources */,
@ -227,6 +267,10 @@
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */,
F83901A4295D864D00456AE2 /* TagView.swift in Sources */,
F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */,
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */,
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */,
F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,15 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
//
import Foundation
import CoreData
@objc(AccountData)
public class AccountData: NSManagedObject {
}

View File

@ -0,0 +1,39 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
//
import Foundation
import CoreData
extension AccountData {
@nonobjc public class func fetchRequest() -> NSFetchRequest<AccountData> {
return NSFetchRequest<AccountData>(entityName: "AccountData")
}
@NSManaged public var id: String?
@NSManaged public var username: String?
@NSManaged public var acct: String?
@NSManaged public var displayName: String?
@NSManaged public var note: String?
@NSManaged public var url: URL?
@NSManaged public var avatar: URL?
@NSManaged public var header: URL?
@NSManaged public var locked: Bool
@NSManaged public var createdAt: String?
@NSManaged public var followersCount: Int32
@NSManaged public var followingCount: Int32
@NSManaged public var statusesCount: Int32
@NSManaged public var accessToken: String?
@NSManaged public var avatarData: Data?
}
extension AccountData : Identifiable {
}

View File

@ -14,8 +14,8 @@ struct PersistenceController {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
let newItem = AccountData(context: viewContext)
newItem.id = "123"
}
do {
try viewContext.save()

View File

@ -0,0 +1,24 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import Foundation
public class ApplicationState: ObservableObject {
public static let shared = ApplicationState()
@Published var accountData: AccountData?
}
extension ApplicationState {
public static var preview: ApplicationState = {
let applicationState = ApplicationState()
applicationState.accountData = AccountData()
return applicationState
}()
}

View File

@ -0,0 +1,24 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import Foundation
public class RemoteFileService {
public static let shared = RemoteFileService()
public func fetchData(url: URL) async throws -> Data? {
let urlRequest = URLRequest(url: url)
// Fetching data.
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
return nil
}
return data
}
}

View File

@ -1,9 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="false" userDefinedModelVersionIdentifier="">
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22C65" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AccountData" representedClassName="AccountData" syncable="YES">
<attribute name="accessToken" optional="YES" attributeType="String"/>
<attribute name="acct" attributeType="String"/>
<attribute name="avatar" optional="YES" attributeType="URI"/>
<attribute name="avatarData" optional="YES" attributeType="Binary"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
<attribute name="followersCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="followingCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="header" optional="YES" attributeType="URI"/>
<attribute name="id" attributeType="String"/>
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="username" attributeType="String"/>
</entity>
<elements>
<element name="Item" positionX="-63" positionY="-18" width="128" height="44"/>
</elements>
</model>

View File

@ -9,12 +9,13 @@ import SwiftUI
@main
struct VernissageApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
NavigationStack {
MainView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(ApplicationState.shared)
}
.navigationViewStyle(.stack)
}

View File

@ -86,6 +86,7 @@ struct DetailsView: View {
.padding(8)
}
}
.navigationBarTitle("Details")
}
}

View File

@ -0,0 +1,20 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
struct FederatedFeedView: View {
var body: some View {
Text("Federated feed")
}
}
struct FederatedFeedView_Previews: PreviewProvider {
static var previews: some View {
FederatedFeedView()
}
}

View File

@ -0,0 +1,128 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
import MastodonSwift
import UIKit
struct HomeFeedView: View {
@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)
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)
}
}
}
}
VStack(alignment:.trailing) {
Spacer()
HStack {
Spacer()
Button {
} label: {
Image(systemName: "plus")
.font(.body)
.padding(16)
.foregroundColor(.white)
.background(.ultraThinMaterial, in: Circle())
}
}
}.padding()
if showLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
}
.refreshable {
do {
try await loadData()
} catch {
print("Error", error)
}
}
.task {
do {
defer {
self.showLoading = false
}
self.showLoading = true
try await loadData()
} catch {
print("Error", error)
}
}
}
private func loadData() async throws {
let accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI2MTQwOCIsImp0aSI6IjZjMDg4N2ZlZThjZjBmZjQ1N2RjZDQ5MTU2YjE2NzYyYmQ2MDQ1NDQ1MmEwYzEyMzVmNDA3YjY2YjFhYjU3ZjBjMTY2YjFmZmIyNjJlZDg2IiwiaWF0IjoxNjcyMDU5MDYwLjI3NDgyMSwibmJmIjoxNjcyMDU5MDYwLjI3NDgyNCwiZXhwIjoxNzAzNTk1MDYwLjI1MzM1Nywic3ViIjoiNjc4MjMiLCJzY29wZXMiOlsicmVhZCIsIndyaXRlIiwiZm9sbG93Il19.kGvg3lW8lF1X1mOTdgGgoXNyzwUIJz5hz5RJKK_WiSoBWDQNadhZDty7XMNF0IAPjxOSi6UaIx2av7_eH_65aNlKFw89bkm8bT_zFQW2V0KbADJ-NmE6X0B_NgU2CNoF5IPn6bhCFHCKMtV6MWAQ_db6DT-LXaGemMY3QimcJzCqQuXI_1ouiZ235T297uEPNTrLwtLq-x_UoO-wx254LStBalDIGDVHAa4by9IT-mvu-QXz7k8pH2NHKoX-9Ql_Y3G9RJJNqoOmWMU45Dyo2HaJKKEb1tkeJ9tA3LIYgbwnEbG2PJ7CE8CXxtakiCIflJZpzzOmq1jXLAsCJ1mHnc77o7NfMaB_hY-f8PEI6d2ttOdH8bNlreF2avznNAIVHg_bf-yv_4wKUCUe0QZMG_yWqOwOk6lyruvboSGKuI5RnYsJbXBoJTGMLON6jVmtiKPbHy-9jNcfFgShAc3D5kTO-8Avj9_RquqEh1TQF_S4ljmganxKzMihyMDLK1OVcXzCFO6FKlCw7YKvbfJk1Qrn9kPBrVDM5jzIyXAmqRd1ivcE9nAdYb2l7KnxW_pi31uT0IdJMpTkZrUQSDMyEnj0HgV6Yd5BDlLG6Cnk8GXATTcU-a1pgE13OtWsCpD2cZQm-tOsFHWBDvY-BA0RtTvQAyEUxRIP9NjHe8rSR90"
let client = MastodonClient(baseURL: URL(string: "https://pixelfed.social")!)
.getAuthenticated(token: accessToken)
self.statuses = try await client.getHomeTimeline(limit: 40)
var imagesCache: [ImageStatus] = []
for item in self.statuses {
let imageStatus = try await self.fetchImage(status: item)
if let imageStatus {
imagesCache.append(imageStatus)
}
}
self.images = imagesCache
}
public func fetchImage(status: Status) async throws -> ImageStatus? {
guard let url = status.mediaAttachments.first?.url, let id = status.mediaAttachments.first?.id 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)
}
}
struct HomeFeedView_Previews: PreviewProvider {
static var previews: some View {
HomeFeedView()
}
}

View File

@ -0,0 +1,20 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
struct LocalFeedView: View {
var body: some View {
Text("Local feed")
}
}
struct LocalFeedView_Previews: PreviewProvider {
static var previews: some View {
LocalFeedView()
}
}

View File

@ -12,122 +12,35 @@ import MastodonSwift
struct MainView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
@EnvironmentObject var applicationState: ApplicationState
@State private var statuses: [Status] = []
@State private var account: Account?
@State private var images: [ImageStatus] = []
@State private var current: ImageStatus? = nil
@State private var navBarTitle: String = "Home feed"
private static let initialColumns = 1
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
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)
}
}
}
}
VStack(alignment:.trailing) {
Spacer()
HStack {
Spacer()
Button {
} label: {
Image(systemName: "plus")
.font(.body)
.padding(16)
.foregroundColor(.white)
.background(.ultraThinMaterial, in: Circle())
}
}
}.padding()
}.refreshable {
do {
try await loadData()
} catch {
print("Error", error)
@State private var navBarTitle: String = "Home"
@State private var viewMode: ViewMode = .home {
didSet {
switch viewMode {
case .home:
self.navBarTitle = "Home"
case .local:
self.navBarTitle = "Local"
case .federated:
self.navBarTitle = "Federated"
case .notifications:
self.navBarTitle = "Notifications"
}
}
}
private enum ViewMode {
case home, local, federated, notifications
}
var body: some View {
self.getMainView()
.navigationBarTitle(navBarTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Button {
navBarTitle = "Home feed"
} label: {
HStack {
Text("Home feed")
Image(systemName: "house")
}
}
Button {
navBarTitle = "Local feed"
} label: {
HStack {
Text("Local feed")
Image(systemName: "text.redaction")
}
}
Button {
navBarTitle = "Global feed"
} label: {
HStack {
Text("Global feed")
Image(systemName: "globe.europe.africa")
}
}
Button {
navBarTitle = "Notifications"
} label: {
HStack {
Text("Notifications")
Image(systemName: "bell.badge")
}
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
ToolbarItem(placement: .navigationBarLeading) {
Button {
// Open settings view.
} label: {
if let avatarUrl = self.account?.avatar {
AsyncImage(url: avatarUrl) { image in
image
.resizable()
.clipShape(Circle())
.shadow(radius: 10)
.aspectRatio(contentMode: .fit)
} placeholder: {
Image(systemName: "person.circle")
}
.frame(height: 25)
.frame(width: 25)
} else {
Image(systemName: "person.circle")
}
}
}
self.getLeadingToolbar()
self.getPrincipalToolbar()
}
.task {
do {
@ -138,57 +51,148 @@ struct MainView: View {
}
}
@ViewBuilder
private func getMainView() -> some View {
switch self.viewMode {
case .home:
HomeFeedView()
case .local:
LocalFeedView()
case .federated:
FederatedFeedView()
case .notifications:
NotificationsView()
}
}
@ToolbarContentBuilder
private func getPrincipalToolbar() -> some ToolbarContent {
ToolbarItem(placement: .principal) {
Menu {
Button {
viewMode = .home
} label: {
HStack {
Text("Home")
Image(systemName: "house")
}
}
Button {
viewMode = .local
} label: {
HStack {
Text("Local")
Image(systemName: "text.redaction")
}
}
Button {
viewMode = .federated
} label: {
HStack {
Text("Global")
Image(systemName: "globe.europe.africa")
}
}
Button {
viewMode = .notifications
} label: {
HStack {
Text("Notifications")
Image(systemName: "bell.badge")
}
}
} label: {
HStack {
Text(navBarTitle)
.font(.headline)
Image(systemName: "chevron.down")
.font(.subheadline)
}
.frame(width: 150)
.foregroundColor(Color.white)
}
}
}
@ToolbarContentBuilder
private func getLeadingToolbar() -> some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
Button {
// Open settings view.
} label: {
if let avatarData = self.applicationState.accountData?.avatarData, let uiImage = UIImage(data: avatarData) {
Image(uiImage: uiImage)
.resizable()
.clipShape(Circle())
.shadow(radius: 10)
.aspectRatio(contentMode: .fit)
.frame(height: 32)
.frame(width: 32)
} else {
Image(systemName: "person.circle")
}
}
}
}
private func loadData() async throws {
// Set account data from database.
let accountDataFromDb = self.getAccountData()
if let accountDataFromDb {
self.applicationState.accountData = accountDataFromDb
return
}
// Retrieve account data from API.
let accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI2MTQwOCIsImp0aSI6IjZjMDg4N2ZlZThjZjBmZjQ1N2RjZDQ5MTU2YjE2NzYyYmQ2MDQ1NDQ1MmEwYzEyMzVmNDA3YjY2YjFhYjU3ZjBjMTY2YjFmZmIyNjJlZDg2IiwiaWF0IjoxNjcyMDU5MDYwLjI3NDgyMSwibmJmIjoxNjcyMDU5MDYwLjI3NDgyNCwiZXhwIjoxNzAzNTk1MDYwLjI1MzM1Nywic3ViIjoiNjc4MjMiLCJzY29wZXMiOlsicmVhZCIsIndyaXRlIiwiZm9sbG93Il19.kGvg3lW8lF1X1mOTdgGgoXNyzwUIJz5hz5RJKK_WiSoBWDQNadhZDty7XMNF0IAPjxOSi6UaIx2av7_eH_65aNlKFw89bkm8bT_zFQW2V0KbADJ-NmE6X0B_NgU2CNoF5IPn6bhCFHCKMtV6MWAQ_db6DT-LXaGemMY3QimcJzCqQuXI_1ouiZ235T297uEPNTrLwtLq-x_UoO-wx254LStBalDIGDVHAa4by9IT-mvu-QXz7k8pH2NHKoX-9Ql_Y3G9RJJNqoOmWMU45Dyo2HaJKKEb1tkeJ9tA3LIYgbwnEbG2PJ7CE8CXxtakiCIflJZpzzOmq1jXLAsCJ1mHnc77o7NfMaB_hY-f8PEI6d2ttOdH8bNlreF2avznNAIVHg_bf-yv_4wKUCUe0QZMG_yWqOwOk6lyruvboSGKuI5RnYsJbXBoJTGMLON6jVmtiKPbHy-9jNcfFgShAc3D5kTO-8Avj9_RquqEh1TQF_S4ljmganxKzMihyMDLK1OVcXzCFO6FKlCw7YKvbfJk1Qrn9kPBrVDM5jzIyXAmqRd1ivcE9nAdYb2l7KnxW_pi31uT0IdJMpTkZrUQSDMyEnj0HgV6Yd5BDlLG6Cnk8GXATTcU-a1pgE13OtWsCpD2cZQm-tOsFHWBDvY-BA0RtTvQAyEUxRIP9NjHe8rSR90"
let client = MastodonClient(baseURL: URL(string: "https://pixelfed.social")!)
.getAuthenticated(token: accessToken)
self.statuses = try await client.getHomeTimeline()
self.account = try await client.verifyCredentials()
// Get account information from server.
let account = try await client.verifyCredentials()
var imagesCache: [ImageStatus] = []
for item in self.statuses {
let imageStatus = try await self.fetchImage(status: item, accessToken: accessToken)
if let imageStatus {
imagesCache.append(imageStatus)
}
// Create account object in database.
let accountData = AccountData(context: viewContext)
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.accessToken = accessToken
// Download avatar image.
if let avatarUrl = account.avatar {
let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl)
accountData.avatarData = avatarData
}
self.images = imagesCache
// Save account data in database and in application state.
try self.viewContext.save()
self.applicationState.accountData = accountData
}
public func fetchImage(status: Status, accessToken: String) async throws -> ImageStatus? {
guard let url = status.mediaAttachments.first?.url, let id = status.mediaAttachments.first?.id else {
return nil
}
let urlRequest = URLRequest(url: url)
// Fetching data.
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
return nil
}
private func getAccountData() -> AccountData? {
let fetchRequest: NSFetchRequest<AccountData> = AccountData.fetchRequest()
// Decoding JSON.
let image = UIImage(data: data)
guard let image = image else {
do {
return try self.viewContext.fetch(fetchRequest).first
}
catch {
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)
}
}

View File

@ -0,0 +1,20 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
struct NotificationsView: View {
var body: some View {
Text("Notifications")
}
}
struct NotificationsView_Previews: PreviewProvider {
static var previews: some View {
NotificationsView()
}
}