Storing account in CoreData.
This commit is contained in:
parent
f140537825
commit
ed8fa69d9a
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ struct DetailsView: View {
|
|||
.padding(8)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Details")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue