Add user profile for other accounts
This commit is contained in:
parent
3c2ee8c592
commit
d2d4844469
|
@ -13,6 +13,16 @@
|
|||
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 */; };
|
||||
F8210DCF2966B600001D9973 /* ImageRowAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DCE2966B600001D9973 /* ImageRowAsync.swift */; };
|
||||
F8210DD52966BB7E001D9973 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = F8210DD42966BB7E001D9973 /* Nuke */; };
|
||||
F8210DD72966BB7E001D9973 /* NukeExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = F8210DD62966BB7E001D9973 /* NukeExtensions */; };
|
||||
F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = F8210DD82966BB7E001D9973 /* NukeUI */; };
|
||||
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DDC2966CF17001D9973 /* StatusData+Status.swift */; };
|
||||
F8210DDF2966CFC7001D9973 /* AttachmentData+Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */; };
|
||||
F8210DE12966D0C4001D9973 /* StatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE02966D0C4001D9973 /* StatusService.swift */; };
|
||||
F8210DE32966D256001D9973 /* Status+StatusData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE22966D256001D9973 /* Status+StatusData.swift */; };
|
||||
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE42966E160001D9973 /* Color+SystemColors.swift */; };
|
||||
F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE62966E1D1001D9973 /* Color+Assets.swift */; };
|
||||
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */; };
|
||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F91295C63BB009C8EE6 /* ImageStatus.swift */; };
|
||||
F83901A6295D8EC000456AE2 /* LabelIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83901A5295D8EC000456AE2 /* LabelIcon.swift */; };
|
||||
|
@ -64,6 +74,13 @@
|
|||
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>"; };
|
||||
F8210DCE2966B600001D9973 /* ImageRowAsync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRowAsync.swift; sourceTree = "<group>"; };
|
||||
F8210DDC2966CF17001D9973 /* StatusData+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+Status.swift"; sourceTree = "<group>"; };
|
||||
F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+Attachment.swift"; sourceTree = "<group>"; };
|
||||
F8210DE02966D0C4001D9973 /* StatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusService.swift; sourceTree = "<group>"; };
|
||||
F8210DE22966D256001D9973 /* Status+StatusData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Status+StatusData.swift"; sourceTree = "<group>"; };
|
||||
F8210DE42966E160001D9973 /* Color+SystemColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+SystemColors.swift"; sourceTree = "<group>"; };
|
||||
F8210DE62966E1D1001D9973 /* Color+Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Assets.swift"; sourceTree = "<group>"; };
|
||||
F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Exif.swift"; sourceTree = "<group>"; };
|
||||
F8341F91295C63BB009C8EE6 /* ImageStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStatus.swift; sourceTree = "<group>"; };
|
||||
F83901A5295D8EC000456AE2 /* LabelIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelIcon.swift; sourceTree = "<group>"; };
|
||||
|
@ -115,6 +132,9 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F866F6B729608467002E8F88 /* MastodonSwift in Frameworks */,
|
||||
F8210DD52966BB7E001D9973 /* Nuke in Frameworks */,
|
||||
F8210DD72966BB7E001D9973 /* NukeExtensions in Frameworks */,
|
||||
F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -143,6 +163,9 @@
|
|||
F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */,
|
||||
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */,
|
||||
F85D49862964334100751DF7 /* String+Date.swift */,
|
||||
F8210DE22966D256001D9973 /* Status+StatusData.swift */,
|
||||
F8210DE42966E160001D9973 /* Color+SystemColors.swift */,
|
||||
F8210DE62966E1D1001D9973 /* Color+Assets.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -163,9 +186,11 @@
|
|||
F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */,
|
||||
F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */,
|
||||
F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */,
|
||||
F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */,
|
||||
F80048012961850500E6868A /* StatusData+CoreDataClass.swift */,
|
||||
F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */,
|
||||
F85D49842964301800751DF7 /* StatusData+Attachments.swift */,
|
||||
F8210DDC2966CF17001D9973 /* StatusData+Status.swift */,
|
||||
F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */,
|
||||
F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */,
|
||||
F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */,
|
||||
|
@ -193,6 +218,7 @@
|
|||
F83901A5295D8EC000456AE2 /* LabelIcon.swift */,
|
||||
F85D4972296406E700751DF7 /* BottomRight.swift */,
|
||||
F85D497629640A5200751DF7 /* ImageRow.swift */,
|
||||
F8210DCE2966B600001D9973 /* ImageRowAsync.swift */,
|
||||
F85D497829640B9D00751DF7 /* ImagesCarousel.swift */,
|
||||
F85D497A29640C8200751DF7 /* UsernameRow.swift */,
|
||||
F85D497C29640D5900751DF7 /* InteractionRow.swift */,
|
||||
|
@ -252,6 +278,7 @@
|
|||
F85D4970296402DC00751DF7 /* AuthorizationService.swift */,
|
||||
F85D4974296407F100751DF7 /* TimelineService.swift */,
|
||||
F8A93D7F2965FED4001D8331 /* AccountService.swift */,
|
||||
F8210DE02966D0C4001D9973 /* StatusService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
|
@ -274,6 +301,9 @@
|
|||
name = Vernissage;
|
||||
packageProductDependencies = (
|
||||
F866F6B629608467002E8F88 /* MastodonSwift */,
|
||||
F8210DD42966BB7E001D9973 /* Nuke */,
|
||||
F8210DD62966BB7E001D9973 /* NukeExtensions */,
|
||||
F8210DD82966BB7E001D9973 /* NukeUI */,
|
||||
);
|
||||
productName = Vernissage;
|
||||
productReference = F88C2468295C37B80006098B /* Vernissage.app */;
|
||||
|
@ -305,6 +335,7 @@
|
|||
mainGroup = F88C245F295C37B80006098B;
|
||||
packageReferences = (
|
||||
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */,
|
||||
F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||
);
|
||||
productRefGroup = F88C2469295C37B80006098B /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -333,8 +364,10 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F85D497729640A5200751DF7 /* ImageRow.swift in Sources */,
|
||||
F8210DDF2966CFC7001D9973 /* AttachmentData+Attachment.swift in Sources */,
|
||||
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
|
||||
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
||||
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */,
|
||||
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
||||
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
|
||||
F85D4975296407F100751DF7 /* TimelineService.swift in Sources */,
|
||||
|
@ -342,9 +375,12 @@
|
|||
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */,
|
||||
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */,
|
||||
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
||||
F8210DE12966D0C4001D9973 /* StatusService.swift in Sources */,
|
||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */,
|
||||
F85D49872964334100751DF7 /* String+Date.swift in Sources */,
|
||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
|
||||
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */,
|
||||
F8210DCF2966B600001D9973 /* ImageRowAsync.swift in Sources */,
|
||||
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
|
||||
F85D497B29640C8200751DF7 /* UsernameRow.swift in Sources */,
|
||||
F85D497929640B9D00751DF7 /* ImagesCarousel.swift in Sources */,
|
||||
|
@ -363,7 +399,9 @@
|
|||
F85D497F296416C800751DF7 /* CommentsSection.swift in Sources */,
|
||||
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
|
||||
F866F6A529604194002E8F88 /* ApplicationSettingsHandler.swift in Sources */,
|
||||
F8210DE32966D256001D9973 /* Status+StatusData.swift in Sources */,
|
||||
F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */,
|
||||
F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */,
|
||||
F85D497D29640D5900751DF7 /* InteractionRow.swift in Sources */,
|
||||
F866F6A729604629002E8F88 /* SignInView.swift in Sources */,
|
||||
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */,
|
||||
|
@ -583,6 +621,14 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kean/Nuke";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mczachurski/Mastodon.swift";
|
||||
|
@ -594,6 +640,21 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
F8210DD42966BB7E001D9973 /* Nuke */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||
productName = Nuke;
|
||||
};
|
||||
F8210DD62966BB7E001D9973 /* NukeExtensions */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||
productName = NukeExtensions;
|
||||
};
|
||||
F8210DD82966BB7E001D9973 /* NukeUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
F866F6B629608467002E8F88 /* MastodonSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */;
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000",
|
||||
"red" : "0.000"
|
||||
"blue" : "68",
|
||||
"green" : "87",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
"blue" : "68",
|
||||
"green" : "87",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
|
@ -8,6 +8,9 @@
|
|||
import Foundation
|
||||
|
||||
class AccountDataHandler {
|
||||
public static let shared = AccountDataHandler()
|
||||
private init() { }
|
||||
|
||||
func getAccountsData() -> [AccountData] {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = AccountData.fetchRequest()
|
||||
|
@ -21,9 +24,7 @@ class AccountDataHandler {
|
|||
|
||||
func getCurrentAccountData() -> AccountData? {
|
||||
let accounts = self.getAccountsData()
|
||||
|
||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
||||
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
|
||||
|
||||
let currentAccount = accounts.first { accountData in
|
||||
accountData.id == defaultSettings.currentAccount
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
import Foundation
|
||||
|
||||
class ApplicationSettingsHandler {
|
||||
public static let shared = ApplicationSettingsHandler()
|
||||
private init() { }
|
||||
|
||||
func getDefaultSettings() -> ApplicationSettings {
|
||||
var settingsList: [ApplicationSettings] = []
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
extension AttachmentData {
|
||||
func copyFrom(_ attachment: Attachment) {
|
||||
self.id = attachment.id
|
||||
self.url = attachment.url
|
||||
self.blurhash = attachment.blurhash
|
||||
self.previewUrl = attachment.previewUrl
|
||||
self.remoteUrl = attachment.remoteUrl
|
||||
self.text = attachment.description
|
||||
self.type = attachment.type.rawValue
|
||||
}
|
||||
}
|
|
@ -9,6 +9,9 @@ import Foundation
|
|||
import CoreData
|
||||
|
||||
class AttachmentDataHandler {
|
||||
public static let shared = AttachmentDataHandler()
|
||||
private init() { }
|
||||
|
||||
func getAttachmentsData() -> [AttachmentData] {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = AttachmentData.fetchRequest()
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
|
||||
import CoreData
|
||||
|
||||
public class CoreDataHandler {
|
||||
public class CoreDataHandler {
|
||||
public static let shared = CoreDataHandler()
|
||||
|
||||
public let container: NSPersistentContainer
|
||||
|
||||
init(inMemory: Bool = false) {
|
||||
private init(inMemory: Bool = false) {
|
||||
container = NSPersistentContainer(name: "Vernissage")
|
||||
if inMemory {
|
||||
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
||||
|
@ -56,6 +57,12 @@ public class CoreDataHandler {
|
|||
}
|
||||
}
|
||||
|
||||
extension CoreDataHandler {
|
||||
public static var memory: CoreDataHandler = {
|
||||
CoreDataHandler(inMemory: true)
|
||||
}()
|
||||
}
|
||||
|
||||
extension CoreDataHandler {
|
||||
public static var preview: CoreDataHandler = {
|
||||
let result = CoreDataHandler(inMemory: true)
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
extension StatusData {
|
||||
func copyFrom(_ status: Status) {
|
||||
self.id = status.id
|
||||
self.createdAt = status.createdAt
|
||||
self.accountAvatar = status.account?.avatar
|
||||
self.accountDisplayName = status.account?.displayName
|
||||
self.accountId = status.account!.id
|
||||
self.accountUsername = status.account!.username
|
||||
self.applicationName = status.application?.name
|
||||
self.applicationWebsite = status.application?.website
|
||||
self.bookmarked = status.bookmarked
|
||||
self.content = status.content
|
||||
self.favourited = status.favourited
|
||||
self.favouritesCount = Int32(status.favouritesCount)
|
||||
self.inReplyToAccount = status.inReplyToAccount
|
||||
self.inReplyToId = status.inReplyToId
|
||||
self.muted = status.muted
|
||||
self.pinned = status.pinned
|
||||
self.reblogged = status.reblogged
|
||||
self.reblogsCount = Int32(status.reblogsCount)
|
||||
self.repliesCount = Int32(status.repliesCount)
|
||||
self.sensitive = status.sensitive
|
||||
self.spoilerText = status.spoilerText
|
||||
self.uri = status.uri
|
||||
self.url = status.url
|
||||
self.visibility = status.visibility.rawValue
|
||||
}
|
||||
}
|
|
@ -7,8 +7,12 @@
|
|||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import MastodonSwift
|
||||
|
||||
class StatusDataHandler {
|
||||
public static let shared = StatusDataHandler()
|
||||
private init() { }
|
||||
|
||||
func getStatusesData() -> [StatusData] {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = StatusData.fetchRequest()
|
||||
|
@ -20,6 +24,21 @@ class StatusDataHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func getStatusData(statusId: String) -> StatusData? {
|
||||
let context = CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = StatusData.fetchRequest()
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.predicate = NSPredicate(format: "id = %@", statusId)
|
||||
|
||||
do {
|
||||
return try context.fetch(fetchRequest).first
|
||||
} catch {
|
||||
print("Error during fetching accounts")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getMaximumStatus(viewContext: NSManagedObjectContext? = nil) -> StatusData? {
|
||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||
let fetchRequest = StatusData.fetchRequest()
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
|
||||
// MARK: - Text Colors
|
||||
static let dangerColor = Color("DangerColor")
|
||||
static let lightGrayColor = Color("LightGrayColor")
|
||||
static let mainTextColor = Color("MainTextColor")
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
|
||||
// MARK: - Text Colors
|
||||
static let lightText = Color(UIColor.lightText)
|
||||
static let darkText = Color(UIColor.darkText)
|
||||
static let placeholderText = Color(UIColor.placeholderText)
|
||||
|
||||
// MARK: - Label Colors
|
||||
static let label = Color(UIColor.label)
|
||||
static let secondaryLabel = Color(UIColor.secondaryLabel)
|
||||
static let tertiaryLabel = Color(UIColor.tertiaryLabel)
|
||||
static let quaternaryLabel = Color(UIColor.quaternaryLabel)
|
||||
|
||||
// MARK: - Background Colors
|
||||
static let systemBackground = Color(UIColor.systemBackground)
|
||||
static let secondarySystemBackground = Color(UIColor.secondarySystemBackground)
|
||||
static let tertiarySystemBackground = Color(UIColor.tertiarySystemBackground)
|
||||
|
||||
// MARK: - Fill Colors
|
||||
static let systemFill = Color(UIColor.systemFill)
|
||||
static let secondarySystemFill = Color(UIColor.secondarySystemFill)
|
||||
static let tertiarySystemFill = Color(UIColor.tertiarySystemFill)
|
||||
static let quaternarySystemFill = Color(UIColor.quaternarySystemFill)
|
||||
|
||||
// MARK: - Grouped Background Colors
|
||||
static let systemGroupedBackground = Color(UIColor.systemGroupedBackground)
|
||||
static let secondarySystemGroupedBackground = Color(UIColor.secondarySystemGroupedBackground)
|
||||
static let tertiarySystemGroupedBackground = Color(UIColor.tertiarySystemGroupedBackground)
|
||||
|
||||
// MARK: - Gray Colors
|
||||
static let systemGray = Color(UIColor.systemGray)
|
||||
static let systemGray2 = Color(UIColor.systemGray2)
|
||||
static let systemGray3 = Color(UIColor.systemGray3)
|
||||
static let systemGray4 = Color(UIColor.systemGray4)
|
||||
static let systemGray5 = Color(UIColor.systemGray5)
|
||||
static let systemGray6 = Color(UIColor.systemGray6)
|
||||
|
||||
// MARK: - Other Colors
|
||||
static let separator = Color(UIColor.separator)
|
||||
static let opaqueSeparator = Color(UIColor.opaqueSeparator)
|
||||
static let link = Color(UIColor.link)
|
||||
|
||||
// MARK: System Colors
|
||||
static let systemBlue = Color(UIColor.systemBlue)
|
||||
static let systemPurple = Color(UIColor.systemPurple)
|
||||
static let systemGreen = Color(UIColor.systemGreen)
|
||||
static let systemYellow = Color(UIColor.systemYellow)
|
||||
static let systemOrange = Color(UIColor.systemOrange)
|
||||
static let systemPink = Color(UIColor.systemPink)
|
||||
static let systemRed = Color(UIColor.systemRed)
|
||||
static let systemTeal = Color(UIColor.systemTeal)
|
||||
static let systemIndigo = Color(UIColor.systemIndigo)
|
||||
}
|
|
@ -18,4 +18,27 @@ extension MastodonClientAuthenticated {
|
|||
let (data, _) = try await urlSession.data(for: request)
|
||||
return try JSONDecoder().decode(Account.self, from: data)
|
||||
}
|
||||
|
||||
func getRelationship(for accountId: String) async throws -> Relationship? {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Account.relationships([accountId]),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
let relationships = try JSONDecoder().decode([Relationship].self, from: data)
|
||||
return relationships.first
|
||||
}
|
||||
|
||||
func getStatuses(for accountId: String) async throws -> [Status] {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Account.statuses(accountId, true, true),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
return try JSONDecoder().decode([Status].self, from: data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
extension Status {
|
||||
func createStatusData() async throws -> StatusData {
|
||||
let statusData = StatusDataHandler.shared.createStatusDataEntity(viewContext: CoreDataHandler.memory.container.viewContext)
|
||||
statusData.copyFrom(self)
|
||||
|
||||
for attachment in self.mediaAttachments {
|
||||
let imageData = try await RemoteFileService.shared.fetchData(url: attachment.url)
|
||||
|
||||
guard let imageData = imageData else {
|
||||
continue
|
||||
}
|
||||
|
||||
// Save attachment in database.
|
||||
let attachmentData = AttachmentDataHandler.shared.createAttachmnentDataEntity(viewContext: CoreDataHandler.memory.container.viewContext)
|
||||
|
||||
attachmentData.copyFrom(attachment)
|
||||
attachmentData.statusId = statusData.id
|
||||
attachmentData.data = imageData
|
||||
|
||||
// TODO: read exif informatio
|
||||
|
||||
attachmentData.statusRelation = statusData
|
||||
statusData.addToAttachmentRelation(attachmentData)
|
||||
}
|
||||
|
||||
return statusData
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ struct HTMLFormattedText: UIViewRepresentable {
|
|||
|
||||
let largeAttributes = [
|
||||
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
||||
NSAttributedString.Key.foregroundColor: UIColor(Color("MainTextColor"))
|
||||
NSAttributedString.Key.foregroundColor: UIColor(Color.mainTextColor)
|
||||
]
|
||||
|
||||
let linkAttributes = [
|
||||
|
|
|
@ -9,6 +9,7 @@ import MastodonSwift
|
|||
|
||||
public class AccountService {
|
||||
public static let shared = AccountService()
|
||||
private init() { }
|
||||
|
||||
public func getAccount(withId accountId: String, and accountData: AccountData?) async throws -> Account? {
|
||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||
|
@ -18,4 +19,22 @@ public class AccountService {
|
|||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.getAccount(for: accountId)
|
||||
}
|
||||
|
||||
public func getRelationship(withId accountId: String, forUser accountData: AccountData?) async throws -> Relationship? {
|
||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.getRelationship(for: accountId)
|
||||
}
|
||||
|
||||
public func getStatuses(forAccountId accountId: String, andContext accountData: AccountData?) async throws -> [Status] {
|
||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||
return []
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.getStatuses(for: accountId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import MastodonSwift
|
|||
|
||||
public class AuthorizationService {
|
||||
public static let shared = AuthorizationService()
|
||||
private init() { }
|
||||
|
||||
public func verifyAccount(_ result: @escaping (AccountData?) -> Void) async {
|
||||
let accountDataHandler = AccountDataHandler()
|
||||
let currentAccount = accountDataHandler.getCurrentAccountData()
|
||||
let currentAccount = AccountDataHandler.shared.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 = currentAccount, let accessToken = accountData.accessToken else {
|
||||
|
@ -65,8 +65,7 @@ public class AuthorizationService {
|
|||
let account = try await authenticatedClient.verifyCredentials()
|
||||
|
||||
// Create account object in database.
|
||||
let accountDataHandler = AccountDataHandler()
|
||||
let accountData = accountDataHandler.createAccountDataEntity()
|
||||
let accountData = AccountDataHandler.shared.createAccountDataEntity()
|
||||
|
||||
accountData.id = account.id
|
||||
accountData.username = account.username
|
||||
|
@ -100,8 +99,7 @@ public class AuthorizationService {
|
|||
}
|
||||
|
||||
// Set newly created account as current.
|
||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
||||
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
|
||||
defaultSettings.currentAccount = accountData.id
|
||||
|
||||
// Save account data in database and in application state.
|
||||
|
@ -158,8 +156,7 @@ public class AuthorizationService {
|
|||
}
|
||||
|
||||
// We have to be sure that account id is saved as default account.
|
||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
||||
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
|
||||
defaultSettings.currentAccount = accountData.id
|
||||
|
||||
// Save account data in database and in application state.
|
||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
|||
|
||||
public class RemoteFileService {
|
||||
public static let shared = RemoteFileService()
|
||||
private init() { }
|
||||
|
||||
public func fetchData(url: URL) async throws -> Data? {
|
||||
let urlRequest = URLRequest(url: url)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
public class StatusService {
|
||||
public static let shared = StatusService()
|
||||
private init() { }
|
||||
|
||||
func copy(from status: Status, to statusData: StatusData) {
|
||||
|
||||
}
|
||||
}
|
|
@ -10,14 +10,14 @@ import MastodonSwift
|
|||
|
||||
public class TimelineService {
|
||||
public static let shared = TimelineService()
|
||||
private init() { }
|
||||
|
||||
public func onBottomOfList(for accountData: AccountData) async throws {
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
// Get maximimum downloaded stauts id.
|
||||
let statusDataHandler = StatusDataHandler()
|
||||
let oldestStatus = statusDataHandler.getMinimumtatus(viewContext: backgroundContext)
|
||||
let oldestStatus = StatusDataHandler.shared.getMinimumtatus(viewContext: backgroundContext)
|
||||
|
||||
guard let oldestStatus = oldestStatus else {
|
||||
return
|
||||
|
@ -31,40 +31,20 @@ public class TimelineService {
|
|||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
// Get maximimum downloaded stauts id.
|
||||
let statusDataHandler = StatusDataHandler()
|
||||
let newestStatus = statusDataHandler.getMaximumStatus(viewContext: backgroundContext)
|
||||
let newestStatus = StatusDataHandler.shared.getMaximumStatus(viewContext: backgroundContext)
|
||||
|
||||
try await self.loadData(for: accountData, on: backgroundContext, minId: newestStatus?.id)
|
||||
}
|
||||
|
||||
public func getStatus(withId statusId: String, and accountData: AccountData) async throws -> Status? {
|
||||
guard let accessToken = accountData.accessToken else {
|
||||
public func getStatus(withId statusId: String, and accountData: AccountData?) async throws -> Status? {
|
||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||
return try await client.read(statusId: statusId)
|
||||
}
|
||||
|
||||
public func updateStatus(statusData: StatusData, and accountData: AccountData) async throws -> StatusData? {
|
||||
guard let accessToken = accountData.accessToken else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
// Get new information from API.
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||
let status = try await client.read(statusId: statusData.id)
|
||||
|
||||
// Update status data in database.
|
||||
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
||||
try backgroundContext.save()
|
||||
|
||||
return statusData
|
||||
}
|
||||
|
||||
public func getComments(for statusId: String, and accountData: AccountData) async throws -> Context {
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? "")
|
||||
return try await client.getContext(for: statusId)
|
||||
|
@ -78,46 +58,29 @@ public class TimelineService {
|
|||
// Retrieve statuses from API.
|
||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 40)
|
||||
|
||||
// Create handler for managing statuses in database.
|
||||
let statusDataHandler = StatusDataHandler()
|
||||
|
||||
|
||||
// Save status data in database.
|
||||
for status in statuses {
|
||||
let statusData = statusDataHandler.createStatusDataEntity(viewContext: backgroundContext)
|
||||
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
||||
let statusData = StatusDataHandler.shared.createStatusDataEntity(viewContext: backgroundContext)
|
||||
try await self.copy(from: status, to: statusData, on: backgroundContext)
|
||||
}
|
||||
|
||||
try backgroundContext.save()
|
||||
}
|
||||
|
||||
private func updateStatusData(from status: Status, to statusData: StatusData, on backgroundContext: NSManagedObjectContext) async throws {
|
||||
statusData.id = status.id
|
||||
statusData.createdAt = status.createdAt
|
||||
statusData.accountAvatar = status.account?.avatar
|
||||
statusData.accountDisplayName = status.account?.displayName
|
||||
statusData.accountId = status.account!.id
|
||||
statusData.accountUsername = status.account!.username
|
||||
statusData.applicationName = status.application?.name
|
||||
statusData.applicationWebsite = status.application?.website
|
||||
statusData.bookmarked = status.bookmarked
|
||||
statusData.content = status.content
|
||||
statusData.favourited = status.favourited
|
||||
statusData.favouritesCount = Int32(status.favouritesCount)
|
||||
statusData.inReplyToAccount = status.inReplyToAccount
|
||||
statusData.inReplyToId = status.inReplyToId
|
||||
statusData.muted = status.muted
|
||||
statusData.pinned = status.pinned
|
||||
statusData.reblogged = status.reblogged
|
||||
statusData.reblogsCount = Int32(status.reblogsCount)
|
||||
statusData.repliesCount = Int32(status.repliesCount)
|
||||
statusData.sensitive = status.sensitive
|
||||
statusData.spoilerText = status.spoilerText
|
||||
statusData.uri = status.uri
|
||||
statusData.url = status.url
|
||||
statusData.visibility = status.visibility.rawValue
|
||||
public func updateStatus(_ statusData: StatusData, basedOn status: Status) async throws -> StatusData? {
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
let attachmentDataHandler = AttachmentDataHandler()
|
||||
// Update status data in database.
|
||||
try await self.copy(from: status, to: statusData, on: backgroundContext)
|
||||
try backgroundContext.save()
|
||||
|
||||
return statusData
|
||||
}
|
||||
|
||||
private func copy(from status: Status, to statusData: StatusData, on backgroundContext: NSManagedObjectContext) async throws {
|
||||
statusData.copyFrom(status)
|
||||
|
||||
for attachment in status.mediaAttachments {
|
||||
let imageData = try await self.fetchImage(attachment: attachment)
|
||||
|
@ -126,31 +89,16 @@ public class TimelineService {
|
|||
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 = statusData.attachments().first { item in item.id == attachment.id }
|
||||
?? attachmentDataHandler.createAttachmnentDataEntity(viewContext: backgroundContext)
|
||||
|
||||
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
|
||||
?? AttachmentDataHandler.shared.createAttachmnentDataEntity(viewContext: backgroundContext)
|
||||
|
||||
attachmentData.copyFrom(attachment)
|
||||
attachmentData.statusId = statusData.id
|
||||
attachmentData.data = imageData
|
||||
|
||||
// TODO: read exif information
|
||||
|
||||
if attachmentData.isInserted {
|
||||
attachmentData.statusRelation = statusData
|
||||
statusData.addToAttachmentRelation(attachmentData)
|
||||
|
|
|
@ -20,6 +20,7 @@ struct VernissageApp: App {
|
|||
NavigationStack {
|
||||
switch applicationViewMode {
|
||||
case .loading:
|
||||
// TODO: Loading splashscreen.
|
||||
Text("Loading")
|
||||
case .signIn:
|
||||
SignInView { viewMode in
|
||||
|
@ -43,6 +44,9 @@ struct VernissageApp: App {
|
|||
self.applicationState.accountData = accountData
|
||||
self.applicationViewMode = .mainView
|
||||
})
|
||||
|
||||
URLCache.shared.memoryCapacity = 10_000_000 // ~10 MB memory space
|
||||
URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
|
|
|
@ -10,64 +10,102 @@ import AVFoundation
|
|||
|
||||
struct DetailsView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
@ObservedObject public var statusData: StatusData
|
||||
@State var statusId: String
|
||||
|
||||
@State private var statusData: StatusData?
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack (alignment: .leading) {
|
||||
ImagesCarousel(attachments: statusData.attachments())
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
NavigationLink(destination: UserProfileView(
|
||||
accountId: statusData.accountId,
|
||||
accountDisplayName: statusData.accountDisplayName,
|
||||
accountUserName: statusData.accountUsername)
|
||||
.environmentObject(applicationState)) {
|
||||
UsernameRow(statusData: statusData)
|
||||
if let statusData = self.statusData {
|
||||
VStack (alignment: .leading) {
|
||||
ImagesCarousel(attachments: statusData.attachments())
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
NavigationLink(destination: UserProfileView(
|
||||
accountId: statusData.accountId,
|
||||
accountDisplayName: statusData.accountDisplayName,
|
||||
accountUserName: statusData.accountUsername)
|
||||
.environmentObject(applicationState)) {
|
||||
UsernameRow(statusData: statusData)
|
||||
}
|
||||
|
||||
HTMLFormattedText(statusData.content)
|
||||
.padding(.leading, -4)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
LabelIcon(iconName: "camera", value: "SONY ILCE-7M3")
|
||||
LabelIcon(iconName: "camera.aperture", value: "Viltrox 24mm F1.8 E")
|
||||
LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100")
|
||||
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
||||
}
|
||||
|
||||
HTMLFormattedText(statusData.content)
|
||||
.padding(.leading, -4)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
LabelIcon(iconName: "camera", value: "SONY ILCE-7M3")
|
||||
LabelIcon(iconName: "camera.aperture", value: "Viltrox 24mm F1.8 E")
|
||||
LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100")
|
||||
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
||||
}
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
|
||||
HStack {
|
||||
Text("Uploaded")
|
||||
Text(statusData.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.padding(.horizontal, -4)
|
||||
if let applicationName = statusData.applicationName {
|
||||
Text("via \(applicationName)")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
|
||||
HStack {
|
||||
Text("Uploaded")
|
||||
Text(statusData.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.padding(.horizontal, -4)
|
||||
if let applicationName = statusData.applicationName {
|
||||
Text("via \(applicationName)")
|
||||
}
|
||||
}
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
|
||||
InteractionRow(statusData: statusData)
|
||||
.padding(8)
|
||||
}
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
.font(.footnote)
|
||||
.padding(8)
|
||||
|
||||
InteractionRow(statusData: statusData)
|
||||
.padding(8)
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color.mainTextColor)
|
||||
.opacity(0.1)
|
||||
|
||||
CommentsSection(statusId: statusData.id)
|
||||
}
|
||||
} else {
|
||||
VStack (alignment: .leading) {
|
||||
Rectangle()
|
||||
.fill(Color.placeholderText)
|
||||
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)
|
||||
.redacted(reason: .placeholder)
|
||||
HStack (alignment: .center) {
|
||||
Circle()
|
||||
.fill(Color.placeholderText)
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
.redacted(reason: .placeholder)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text("Verylong Displayname")
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
.redacted(reason: .placeholder)
|
||||
Text("@username")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
.redacted(reason: .placeholder)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
}.padding(8)
|
||||
}
|
||||
.padding(8)
|
||||
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color("MainTextColor"))
|
||||
.opacity(0.1)
|
||||
|
||||
CommentsSection(statusId: statusData.id)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Details")
|
||||
.onAppear {
|
||||
Task {
|
||||
do {
|
||||
if let accountData = self.applicationState.accountData {
|
||||
let timelineService = TimelineService()
|
||||
_ = try await timelineService.updateStatus(statusData: self.statusData, and: accountData)
|
||||
// Get status from API.
|
||||
let status = try await TimelineService.shared.getStatus(withId: self.statusId, and: self.applicationState.accountData)
|
||||
|
||||
if let status {
|
||||
// Get status from database.
|
||||
let statusDataFromDatabase = StatusDataHandler.shared.getStatusData(statusId: self.statusId)
|
||||
|
||||
// If we have status in database then we can update data.
|
||||
if let statusDataFromDatabase {
|
||||
self.statusData = try await TimelineService.shared.updateStatus(statusDataFromDatabase, basedOn: status)
|
||||
} else {
|
||||
self.statusData = try await status.createStatusData()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
|
@ -79,6 +117,6 @@ struct DetailsView: View {
|
|||
|
||||
struct DetailsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DetailsView(statusData: StatusData())
|
||||
DetailsView(statusId: "123")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,8 @@ struct HomeFeedView: View {
|
|||
ScrollView {
|
||||
LazyVGrid(columns: gridColumns) {
|
||||
ForEach(dbStatuses, id: \.self) { item in
|
||||
NavigationLink(destination:
|
||||
DetailsView(statusData: item)
|
||||
.environmentObject(applicationState)) {
|
||||
NavigationLink(destination: DetailsView(statusId: item.id)
|
||||
.environmentObject(applicationState)) {
|
||||
ImageRow(attachments: item.attachments())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ struct MainView: View {
|
|||
.font(.subheadline)
|
||||
}
|
||||
.frame(width: 150)
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,20 +108,20 @@ struct MainView: View {
|
|||
Menu {
|
||||
|
||||
Button {
|
||||
// Switch accounts...
|
||||
// TODO: Switch accounts.
|
||||
} label: {
|
||||
HStack {
|
||||
Text(self.applicationState.accountData?.displayName ?? self.applicationState.accountData?.username ?? "")
|
||||
Image(systemName: "person.circle.fill")
|
||||
.resizable()
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
// Open settings...
|
||||
// TODO: Open settings.
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Settings")
|
||||
|
@ -138,7 +138,7 @@ struct MainView: View {
|
|||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.frame(width: 32.0, height: 32.0)
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ struct SignInView: View {
|
|||
|
||||
var body: some View {
|
||||
VStack {
|
||||
// TODO: Rebild signin.
|
||||
HStack {
|
||||
TextField(
|
||||
"Server address",
|
||||
|
|
|
@ -13,103 +13,121 @@ struct UserProfileView: View {
|
|||
@State public var accountDisplayName: String?
|
||||
@State public var accountUserName: String
|
||||
@State private var account: Account? = nil
|
||||
@State private var relationship: Relationship? = nil
|
||||
@State private var statuses: [Status] = []
|
||||
|
||||
private static let initialColumns = 1
|
||||
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
ScrollView {
|
||||
if let account = self.account {
|
||||
|
||||
HStack(alignment: .center) {
|
||||
AsyncImage(url: account.avatar) { image in
|
||||
image
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
}
|
||||
.frame(width: 96.0, height: 96.0)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.statusesCount)")
|
||||
.font(.title3)
|
||||
Text("Posts")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
AsyncImage(url: account.avatar) { image in
|
||||
image
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
.frame(width: 96.0, height: 96.0)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.statusesCount)")
|
||||
.font(.title3)
|
||||
Text("Posts")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followersCount)")
|
||||
.font(.title3)
|
||||
Text("Followers")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followingCount)")
|
||||
.font(.title3)
|
||||
Text("Following")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followersCount)")
|
||||
.font(.title3)
|
||||
Text("Followers")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
HStack (alignment: .center) {
|
||||
Text(account.displayName ?? account.username)
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(account.username)")
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// TODO: Folllow/Unfollow.
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: relationship?.following == true ? "person.badge.minus" : "person.badge.plus")
|
||||
Text(relationship?.following == true ? "Unfollow" : (relationship?.followedBy == true ? "Follow back" : "Follow"))
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(relationship?.following == true ? Color.dangerColor : .accentColor)
|
||||
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("\(account.followingCount)")
|
||||
.font(.title3)
|
||||
Text("Following")
|
||||
.font(.subheadline)
|
||||
.opacity(0.6)
|
||||
if let note = account.note {
|
||||
HTMLFormattedText(note, withFontSize: 14, andWidth: Int(UIScreen.main.bounds.width) - 16)
|
||||
.padding(.top, -10)
|
||||
.padding(.leading, -4)
|
||||
}
|
||||
}
|
||||
|
||||
HStack (alignment: .center) {
|
||||
Text(account.displayName ?? account.username)
|
||||
.foregroundColor(Color("DisplayNameColor"))
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(account.username)")
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
|
||||
Text("Joined \(account.createdAt.toRelative(.isoDateTimeMilliSec))")
|
||||
.foregroundColor(Color.lightGrayColor.opacity(0.5))
|
||||
.font(.footnote)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// Folllow/Unfollow
|
||||
} label: {
|
||||
Text("Follow")
|
||||
}
|
||||
.padding()
|
||||
|
||||
LazyVGrid(columns: gridColumns) {
|
||||
ForEach(self.statuses, id: \.id) { item in
|
||||
NavigationLink(destination: DetailsView(statusId: item.id)
|
||||
.environmentObject(applicationState)) {
|
||||
ImageRowAsync(attachments: item.mediaAttachments)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.accentColor)
|
||||
|
||||
}
|
||||
|
||||
if let note = account.note {
|
||||
HTMLFormattedText(note, withFontSize: 14, andWidth: Int(UIScreen.main.bounds.width) - 16)
|
||||
.padding(.top, -10)
|
||||
.padding(.leading, -4)
|
||||
}
|
||||
|
||||
Text("Joined \(account.createdAt.toRelative(.isoDateTimeMilliSec))")
|
||||
.foregroundColor(Color("LightGrayColor").opacity(0.5))
|
||||
.font(.footnote)
|
||||
|
||||
Spacer()
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.navigationBarTitle(self.accountDisplayName ?? self.accountUserName)
|
||||
.onAppear {
|
||||
Task {
|
||||
do {
|
||||
if let account = try await AccountService.shared.getAccount(
|
||||
withId: self.accountId,
|
||||
and: self.applicationState.accountData
|
||||
) {
|
||||
self.account = account
|
||||
}
|
||||
async let relationshipTask = AccountService.shared.getRelationship(withId: self.accountId, forUser: self.applicationState.accountData)
|
||||
async let accountTask = AccountService.shared.getAccount(withId: self.accountId, and: self.applicationState.accountData)
|
||||
|
||||
(self.relationship, self.account) = try await (relationshipTask, accountTask)
|
||||
|
||||
self.statuses = try await AccountService.shared.getStatuses(forAccountId: self.accountId, andContext: self.applicationState.accountData)
|
||||
} catch {
|
||||
print("Error \(error.localizedDescription)")
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ struct CommentsSection: View {
|
|||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
.frame(width: 32.0, height: 32.0)
|
||||
}
|
||||
|
@ -45,17 +45,17 @@ struct CommentsSection: View {
|
|||
VStack (alignment: .leading) {
|
||||
HStack (alignment: .top) {
|
||||
Text(status.account?.displayName ?? status.account?.username ?? "")
|
||||
.foregroundColor(Color("DisplayNameColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
.font(.footnote)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(status.account?.username ?? "")")
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(status.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||
.foregroundColor(Color("LightGrayColor").opacity(0.5))
|
||||
.foregroundColor(Color.lightGrayColor.opacity(0.5))
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
|
@ -73,14 +73,14 @@ struct CommentsSection: View {
|
|||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
||||
.cornerRadius(10)
|
||||
.shadow(color: Color("MainTextColor").opacity(0.3), radius: 2)
|
||||
.shadow(color: Color.mainTextColor.opacity(0.3), radius: 2)
|
||||
} placeholder: {
|
||||
Image(systemName: "photo")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
.opacity(0.05)
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ struct CommentsSection: View {
|
|||
if withDivider {
|
||||
Rectangle()
|
||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||
.fill(Color("MainTextColor"))
|
||||
.fill(Color.mainTextColor)
|
||||
.opacity(0.1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,6 @@ struct ImageRow: View {
|
|||
}.padding()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import NukeUI
|
||||
|
||||
struct ImageRowAsync: View {
|
||||
@State public var attachments: [Attachment]
|
||||
@State private var imageHeight = UIScreen.main.bounds.width
|
||||
|
||||
var body: some View {
|
||||
if let attachment = attachments.first {
|
||||
ZStack {
|
||||
LazyImage(url: attachment.url, resizingMode: .fill)
|
||||
.onSuccess({ imageResponse in
|
||||
let imgHeight = imageResponse.image.size.height
|
||||
let imgWidth = imageResponse.image.size.width
|
||||
|
||||
let divider = imgWidth / UIScreen.main.bounds.size.width
|
||||
self.imageHeight = imgHeight / divider
|
||||
})
|
||||
.frame(height: self.imageHeight <= 0 ? UIScreen.main.bounds.width : self.imageHeight)
|
||||
|
||||
if let count = attachments.count, count > 1 {
|
||||
BottomRight {
|
||||
Text("1 / \(count)")
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 3)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.black)
|
||||
.background(.ultraThinMaterial, in: Capsule())
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageRowAsync_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ImageRow(attachments: [])
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ struct InteractionRow: View {
|
|||
var body: some View {
|
||||
HStack (alignment: .top) {
|
||||
Button {
|
||||
// Reply
|
||||
// TODO: Reply.
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: "message")
|
||||
|
@ -24,7 +24,7 @@ struct InteractionRow: View {
|
|||
Spacer()
|
||||
|
||||
Button {
|
||||
// Reboost
|
||||
// TODO: Reboost.
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: statusData.reblogged ? "paperplane.fill" : "paperplane")
|
||||
|
@ -36,7 +36,7 @@ struct InteractionRow: View {
|
|||
Spacer()
|
||||
|
||||
Button {
|
||||
// Favorite
|
||||
// TODO: Favorite.
|
||||
} label: {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: statusData.favourited ? "hand.thumbsup.fill" : "hand.thumbsup")
|
||||
|
@ -48,7 +48,7 @@ struct InteractionRow: View {
|
|||
Spacer()
|
||||
|
||||
Button {
|
||||
// Bookmark
|
||||
// TODO: Bookmark.
|
||||
} label: {
|
||||
Image(systemName: statusData.bookmarked ? "bookmark.fill" : "bookmark")
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ struct InteractionRow: View {
|
|||
Spacer()
|
||||
|
||||
Button {
|
||||
// Share
|
||||
// TODO: Share.
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
|
|
|
@ -19,15 +19,15 @@ struct UsernameRow: View {
|
|||
} placeholder: {
|
||||
Image(systemName: "person.circle")
|
||||
.resizable()
|
||||
.foregroundColor(Color("MainTextColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
}
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text(statusData.accountDisplayName ?? statusData.accountUsername)
|
||||
.foregroundColor(Color("DisplayNameColor"))
|
||||
.foregroundColor(Color.mainTextColor)
|
||||
Text("@\(statusData.accountUsername)")
|
||||
.foregroundColor(Color("LightGrayColor"))
|
||||
.foregroundColor(Color.lightGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
|
|
Loading…
Reference in New Issue