Add information about last seen status.

This commit is contained in:
Marcin Czachursk 2023-01-31 12:20:49 +01:00
parent 34b3efa211
commit da3db13bc4
42 changed files with 473 additions and 244 deletions

View File

@ -97,12 +97,12 @@
F897978F29684BCB00B22335 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897978E29684BCB00B22335 /* LoadingView.swift */; };
F8984E4D296B648000A2610F /* UIImage+Blurhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */; };
F898DE702972868A004B4A6A /* String+Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = F898DE6F2972868A004B4A6A /* String+Empty.swift */; };
F898DE7229728CB2004B4A6A /* CommentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F898DE7129728CB2004B4A6A /* CommentViewModel.swift */; };
F898DE7229728CB2004B4A6A /* CommentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F898DE7129728CB2004B4A6A /* CommentModel.swift */; };
F8996DEB2971D29D0043EEC6 /* View+Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8996DEA2971D29D0043EEC6 /* View+Transition.swift */; };
F89992C7296D3DF8005994BF /* MastodonKit in Frameworks */ = {isa = PBXBuildFile; productRef = F89992C6296D3DF8005994BF /* MastodonKit */; };
F89992C9296D6DC7005994BF /* CommentBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89992C8296D6DC7005994BF /* CommentBody.swift */; };
F89992CC296D9231005994BF /* StatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89992CB296D9231005994BF /* StatusViewModel.swift */; };
F89992CE296D92E7005994BF /* AttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89992CD296D92E7005994BF /* AttachmentViewModel.swift */; };
F89992CC296D9231005994BF /* StatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89992CB296D9231005994BF /* StatusModel.swift */; };
F89992CE296D92E7005994BF /* AttachmentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89992CD296D92E7005994BF /* AttachmentModel.swift */; };
F89A46DC296EAACE0062125F /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89A46DB296EAACE0062125F /* SettingsView.swift */; };
F89A46DE296EABA20062125F /* StatusPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89A46DD296EABA20062125F /* StatusPlaceholder.swift */; };
F89CEB802984198600A1376F /* AttachmentData+HighestImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89CEB7F2984198600A1376F /* AttachmentData+HighestImage.swift */; };
@ -117,6 +117,8 @@
F8B1E6512973FB7E00EE0D10 /* ToastrService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B1E6502973FB7E00EE0D10 /* ToastrService.swift */; };
F8C14392296AF0B3001FE31D /* String+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14391296AF0B3001FE31D /* String+Exif.swift */; };
F8C14394296AF21B001FE31D /* Double+Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14393296AF21B001FE31D /* Double+Round.swift */; };
F8C5E55F2988E92600ADF6A7 /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5E55E2988E92600ADF6A7 /* AccountModel.swift */; };
F8C5E56229892CC300ADF6A7 /* FirstAppear.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5E56129892CC300ADF6A7 /* FirstAppear.swift */; };
F8C7EDBF298169EE002843BC /* TagsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C7EDBE298169EE002843BC /* TagsService.swift */; };
F8CC95CE2970761D00C9C2AC /* TintColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CC95CD2970761D00C9C2AC /* TintColor.swift */; };
/* End PBXBuildFile section */
@ -210,11 +212,11 @@
F897978E29684BCB00B22335 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Blurhash.swift"; sourceTree = "<group>"; };
F898DE6F2972868A004B4A6A /* String+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Empty.swift"; sourceTree = "<group>"; };
F898DE7129728CB2004B4A6A /* CommentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentViewModel.swift; sourceTree = "<group>"; };
F898DE7129728CB2004B4A6A /* CommentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentModel.swift; sourceTree = "<group>"; };
F8996DEA2971D29D0043EEC6 /* View+Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Transition.swift"; sourceTree = "<group>"; };
F89992C8296D6DC7005994BF /* CommentBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentBody.swift; sourceTree = "<group>"; };
F89992CB296D9231005994BF /* StatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusViewModel.swift; sourceTree = "<group>"; };
F89992CD296D92E7005994BF /* AttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewModel.swift; sourceTree = "<group>"; };
F89992CB296D9231005994BF /* StatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusModel.swift; sourceTree = "<group>"; };
F89992CD296D92E7005994BF /* AttachmentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentModel.swift; sourceTree = "<group>"; };
F89A46DB296EAACE0062125F /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
F89A46DD296EABA20062125F /* StatusPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPlaceholder.swift; sourceTree = "<group>"; };
F89CEB7F2984198600A1376F /* AttachmentData+HighestImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+HighestImage.swift"; sourceTree = "<group>"; };
@ -228,7 +230,10 @@
F8B1E6502973FB7E00EE0D10 /* ToastrService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastrService.swift; sourceTree = "<group>"; };
F8C14391296AF0B3001FE31D /* String+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Exif.swift"; sourceTree = "<group>"; };
F8C14393296AF21B001FE31D /* Double+Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Round.swift"; sourceTree = "<group>"; };
F8C5E55E2988E92600ADF6A7 /* AccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountModel.swift; sourceTree = "<group>"; };
F8C5E56129892CC300ADF6A7 /* FirstAppear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAppear.swift; sourceTree = "<group>"; };
F8C7EDBE298169EE002843BC /* TagsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsService.swift; sourceTree = "<group>"; };
F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-001.xcdatamodel"; sourceTree = "<group>"; };
F8CC95CD2970761D00C9C2AC /* TintColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColor.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -316,6 +321,10 @@
F8341F95295C640C009C8EE6 /* Models */ = {
isa = PBXGroup;
children = (
F89992CB296D9231005994BF /* StatusModel.swift */,
F89992CD296D92E7005994BF /* AttachmentModel.swift */,
F898DE7129728CB2004B4A6A /* CommentModel.swift */,
F8C5E55E2988E92600ADF6A7 /* AccountModel.swift */,
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */,
F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */,
F8CC95CD2970761D00C9C2AC /* TintColor.swift */,
@ -417,12 +426,12 @@
children = (
F866F6A829604FFF002E8F88 /* Info.plist */,
F802884D297AEEAA000BDD51 /* Errors */,
F89992CA296D9211005994BF /* ViewModels */,
F86B721F296C498B00EE59EC /* Styles */,
F88ABD9029686F00004EF61E /* Cache */,
F897978B2968367E00B22335 /* Haptics */,
F8210DE82966E4D8001D9973 /* Modifiers */,
F88FAD30295F5010009B20C9 /* Services */,
F8C5E56029892C8A00ADF6A7 /* ViewModifiers */,
F83901A2295D863B00456AE2 /* Widgets */,
F8341F96295C6427009C8EE6 /* CoreData */,
F8341F95295C640C009C8EE6 /* Models */,
@ -485,16 +494,6 @@
name = Frameworks;
sourceTree = "<group>";
};
F89992CA296D9211005994BF /* ViewModels */ = {
isa = PBXGroup;
children = (
F89992CB296D9231005994BF /* StatusViewModel.swift */,
F89992CD296D92E7005994BF /* AttachmentViewModel.swift */,
F898DE7129728CB2004B4A6A /* CommentViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
F89D6C4029717FC0001DA3D4 /* SettingsView */ = {
isa = PBXGroup;
children = (
@ -525,6 +524,14 @@
path = StatusView;
sourceTree = "<group>";
};
F8C5E56029892C8A00ADF6A7 /* ViewModifiers */ = {
isa = PBXGroup;
children = (
F8C5E56129892CC300ADF6A7 /* FirstAppear.swift */,
);
path = ViewModifiers;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -646,10 +653,11 @@
F89D6C4429718092001DA3D4 /* AccentsSection.swift in Sources */,
F88E4D42297E69FD0057491A /* StatusesView.swift in Sources */,
F85D497929640B9D00751DF7 /* ImagesCarousel.swift in Sources */,
F8C5E55F2988E92600ADF6A7 /* AccountModel.swift in Sources */,
F89D6C3F29716E41001DA3D4 /* Theme.swift in Sources */,
F88E4D5A297ECEE60057491A /* SearchService.swift in Sources */,
F8CC95CE2970761D00C9C2AC /* TintColor.swift in Sources */,
F89992CC296D9231005994BF /* StatusViewModel.swift in Sources */,
F89992CC296D9231005994BF /* StatusModel.swift in Sources */,
F80048052961850500E6868A /* StatusData+CoreDataClass.swift in Sources */,
F86B7221296C49A300EE59EC /* EmptyButtonStyle.swift in Sources */,
F80048042961850500E6868A /* AttachmentData+CoreDataProperties.swift in Sources */,
@ -658,7 +666,7 @@
F83901A6295D8EC000456AE2 /* LabelIcon.swift in Sources */,
F8B1E6512973FB7E00EE0D10 /* ToastrService.swift in Sources */,
F88E4D48297E90CD0057491A /* TrendStatusesView.swift in Sources */,
F89992CE296D92E7005994BF /* AttachmentViewModel.swift in Sources */,
F89992CE296D92E7005994BF /* AttachmentModel.swift in Sources */,
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */,
F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */,
F897978D2968369600B22335 /* HapticService.swift in Sources */,
@ -670,7 +678,7 @@
F86B721E296C458700EE59EC /* BlurredImage.swift in Sources */,
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
F88E4D52297EA6DA0057491A /* String+Markdown.swift in Sources */,
F898DE7229728CB2004B4A6A /* CommentViewModel.swift in Sources */,
F898DE7229728CB2004B4A6A /* CommentModel.swift in Sources */,
F89A46DE296EABA20062125F /* StatusPlaceholder.swift in Sources */,
F88C2482295C3A4F0006098B /* StatusView.swift in Sources */,
F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */,
@ -704,6 +712,7 @@
F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */,
F88E4D54297EA7EE0057491A /* MarkdownFormattedText.swift in Sources */,
F866F6A1296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift in Sources */,
F8C5E56229892CC300ADF6A7 /* FirstAppear.swift in Sources */,
F8C14394296AF21B001FE31D /* Double+Round.swift in Sources */,
F83CBEFB298298A1002972C8 /* ImageCarouselPicture.swift in Sources */,
F89A46DC296EAACE0062125F /* SettingsView.swift in Sources */,
@ -1014,9 +1023,10 @@
F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */,
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */,
);
currentVersion = F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */;
currentVersion = F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */;
path = Vernissage.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@ -34,7 +34,7 @@ extension AccountData {
@NSManaged public var url: URL?
@NSManaged public var username: String
@NSManaged public var statuses: Set<StatusData>?
@NSManaged public var lastSeenStatusId: String?
}
// MARK: Generated accessors for statuses

View File

@ -0,0 +1,55 @@
//
// https://mczachurski.dev
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import Foundation
public class AccountModel: ObservableObject {
public let accessToken: String?
public let refreshToken: String?
public let acct: String
public let avatar: URL?
public let avatarData: Data?
public let clientId: String
public let clientSecret: String
public let clientVapidKey: String
public let createdAt: String
public let displayName: String?
public let followersCount: Int32
public let followingCount: Int32
public let header: URL?
public let id: String
public let locked: Bool
public let note: String?
public let serverUrl: URL
public let statusesCount: Int32
public let url: URL?
public let username: String
public let lastSeenStatusId: String?
init(accountData: AccountData) {
self.accessToken = accountData.accessToken
self.refreshToken = accountData.refreshToken
self.acct = accountData.acct
self.avatar = accountData.avatar
self.avatarData = accountData.avatarData
self.clientId = accountData.clientId
self.clientSecret = accountData.clientSecret
self.clientVapidKey = accountData.clientVapidKey
self.createdAt = accountData.createdAt
self.displayName = accountData.displayName
self.followersCount = accountData.followersCount
self.followingCount = accountData.followingCount
self.header = accountData.header
self.id = accountData.id
self.locked = accountData.locked
self.note = accountData.note
self.serverUrl = accountData.serverUrl
self.statusesCount = accountData.statusesCount
self.url = accountData.url
self.username = accountData.username
self.lastSeenStatusId = accountData.lastSeenStatusId
}
}

View File

@ -12,7 +12,9 @@ public class ApplicationState: ObservableObject {
public static let shared = ApplicationState()
private init() { }
@Published var accountData: AccountData?
@Published var account: AccountModel?
@Published var lastSeenStatusId: String?
@Published var tintColor = TintColor.accentColor2
@Published var theme = Theme.system
@Published var avatarShape = AvatarShape.circle

View File

@ -7,7 +7,7 @@
import Foundation
import MastodonKit
public class AttachmentViewModel: ObservableObject {
public class AttachmentModel: ObservableObject {
public let id: String
public let type: MediaAttachment.MediaAttachmentType
public let url: URL
@ -117,8 +117,8 @@ public class AttachmentViewModel: ObservableObject {
}
}
extension [AttachmentViewModel] {
func getHighestImage() -> AttachmentViewModel? {
extension [AttachmentModel] {
func getHighestImage() -> AttachmentModel? {
var attachment = self.first
var imgHeight = 0.0

View File

@ -6,7 +6,7 @@
import Foundation
public struct CommentViewModel {
var status: StatusViewModel
public struct CommentModel {
var status: StatusModel
var showDivider: Bool
}

View File

@ -7,7 +7,7 @@
import Foundation
import MastodonKit
public class StatusViewModel: ObservableObject {
public class StatusModel: ObservableObject {
public let id: EntityId
public let content: Html
@ -38,7 +38,7 @@ public class StatusViewModel: ObservableObject {
public let reblogStatus: Status?
@Published public var mediaAttachments: [AttachmentViewModel]
@Published public var mediaAttachments: [AttachmentModel]
public init(
id: EntityId,
@ -62,7 +62,7 @@ public class StatusViewModel: ObservableObject {
muted: Bool = false,
spoilerText: String? = nil,
visibility: Status.Visibility = Status.Visibility.pub,
mediaAttachments: [AttachmentViewModel] = [],
mediaAttachments: [AttachmentModel] = [],
card: PreviewCard? = nil,
mentions: [Mention] = [],
tags: [Tag] = [],
@ -126,9 +126,9 @@ public class StatusViewModel: ObservableObject {
self.application = orginalStatus.application
self.place = orginalStatus.place
var mediaAttachments: [AttachmentViewModel] = []
var mediaAttachments: [AttachmentModel] = []
for item in orginalStatus.mediaAttachments {
mediaAttachments.append(AttachmentViewModel(attachment: item))
mediaAttachments.append(AttachmentModel(attachment: item))
}
self.mediaAttachments = mediaAttachments
@ -141,7 +141,7 @@ public class StatusViewModel: ObservableObject {
}
}
public extension StatusViewModel {
public extension StatusModel {
func getImageWidth() -> Int32? {
let highestImage = self.mediaAttachments.getHighestImage()
if let width = (highestImage?.meta as? ImageMetadata)?.original?.width {
@ -162,13 +162,13 @@ public extension StatusViewModel {
}
public extension [Status] {
func toStatusViewModel() -> [StatusViewModel] {
func toStatusViewModel() -> [StatusModel] {
self
.sorted(by: { lhs, rhs in
lhs.id < rhs.id
})
.map { status in
StatusViewModel(status: status)
StatusModel(status: status)
}
}
}

View File

@ -11,8 +11,8 @@ public class AccountService {
public static let shared = AccountService()
private init() { }
public func account(withId accountId: String, for accountData: AccountData?) async throws -> Account? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func account(withId accountId: String, for account: AccountModel?) async throws -> Account? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -20,8 +20,8 @@ public class AccountService {
return try await client.account(for: accountId)
}
public func relationships(withId accountId: String, for accountData: AccountData?) async throws -> Relationship? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func relationships(withId accountId: String, for account: AccountModel?) async throws -> Relationship? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -30,14 +30,14 @@ public class AccountService {
}
public func statuses(createdBy accountId: String,
for accountData: AccountData?,
for account: AccountModel?,
onlyMedia: Bool = true,
excludeReplies: Bool = true,
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
@ -51,8 +51,8 @@ public class AccountService {
limit: limit)
}
public func follow(account accountId: String, for accountData: AccountData?) async throws -> Relationship? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func follow(account accountId: String, for account: AccountModel?) async throws -> Relationship? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -60,8 +60,8 @@ public class AccountService {
return try await client.follow(for: accountId)
}
public func unfollow(account accountId: String, for accountData: AccountData?) async throws -> Relationship? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func unfollow(account accountId: String, for account: AccountModel?) async throws -> Relationship? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -69,8 +69,8 @@ public class AccountService {
return try await client.unfollow(for: accountId)
}
public func mute(account accountId: String, for accountData: AccountData?) async throws -> Relationship? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func mute(account accountId: String, for account: AccountModel?) async throws -> Relationship? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -78,8 +78,8 @@ public class AccountService {
return try await client.mute(for: accountId)
}
public func unmute(account accountId: String, for accountData: AccountData?) async throws -> Relationship? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func unmute(account accountId: String, for account: AccountModel?) async throws -> Relationship? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -87,8 +87,8 @@ public class AccountService {
return try await client.unmute(for: accountId)
}
public func block(account accountId: String, for accountData: AccountData?) async throws -> Relationship? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func block(account accountId: String, for account: AccountModel?) async throws -> Relationship? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -96,8 +96,8 @@ public class AccountService {
return try await client.block(for: accountId)
}
public func unblock(account accountId: String, for accountData: AccountData?) async throws -> Relationship? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func unblock(account accountId: String, for account: AccountModel?) async throws -> Relationship? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -105,8 +105,8 @@ public class AccountService {
return try await client.unblock(for: accountId)
}
public func followers(account accountId: String, for accountData: AccountData?, page: Int) async throws -> [Account] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func followers(account accountId: String, for account: AccountModel?, page: Int) async throws -> [Account] {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
@ -114,8 +114,8 @@ public class AccountService {
return try await client.followers(for: accountId, page: page)
}
public func following(account accountId: String, for accountData: AccountData?, page: Int) async throws -> [Account] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func following(account accountId: String, for account: AccountModel?, page: Int) async throws -> [Account] {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
@ -123,12 +123,12 @@ public class AccountService {
return try await client.following(for: accountId, page: page)
}
public func favourites(for accountData: AccountData?,
public func favourites(for account: AccountModel?,
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
@ -136,12 +136,12 @@ public class AccountService {
return try await client.favourites(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
}
public func bookmarks(for accountData: AccountData?,
public func bookmarks(for account: AccountModel?,
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}

View File

@ -45,7 +45,7 @@ public class AuthorizationService {
}
/// Sign in to the Pixelfed server.
public func sign(in serverAddress: String, session: AuthorizationSession, _ result: @escaping (AccountData?) -> Void) async throws {
public func sign(in serverAddress: String, session: AuthorizationSession, _ result: @escaping (AccountData) -> Void) async throws {
guard let baseUrl = URL(string: serverAddress) else {
throw AuthorisationError.badServerUrl

View File

@ -13,35 +13,56 @@ public class HomeTimelineService {
public static let shared = HomeTimelineService()
private init() { }
public func loadOnBottom(for accountData: AccountData) async throws -> Int {
public func loadOnBottom(for account: AccountModel) async throws -> Int {
// Load data from API and operate on CoreData on background context.
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
// Get maximimum downloaded stauts id.
let oldestStatus = StatusDataHandler.shared.getMinimumStatus(accountId: accountData.id, viewContext: backgroundContext)
let oldestStatus = StatusDataHandler.shared.getMinimumStatus(accountId: account.id, viewContext: backgroundContext)
guard let oldestStatus = oldestStatus else {
return 0
}
let newStatuses = try await self.load(for: accountData, on: backgroundContext, maxId: oldestStatus.id)
// Load data on bottom of the list.
let newStatuses = try await self.load(for: account, on: backgroundContext, maxId: oldestStatus.id)
// Save data into database.
try backgroundContext.save()
// Return amount of newly downloaded statuses.
return newStatuses.count
}
public func loadOnTop(for accountData: AccountData) async throws {
public func loadOnTop(for account: AccountModel) async throws -> String? {
// Load data from API and operate on CoreData on background context.
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
// Refresh/load home timeline (refreshing on top downloads always first 40 items).
// TODO: When Apple introduce good way to show new items without scroll to top then we can change that method.
try await self.refresh(for: accountData, on: backgroundContext)
let lastSeenStatusId = try await self.refresh(for: account, on: backgroundContext)
// Save data into database.
try backgroundContext.save()
// Return id of last seen status.
return lastSeenStatusId
}
public func save(lastSeenStatusId: String, for account: AccountModel) async throws {
// Load data from API and operate on CoreData on background context.
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
// Save information about last seen status.
guard let accountDataFromDb = AccountDataHandler.shared.getAccountData(accountId: account.id, viewContext: backgroundContext) else {
throw DatabaseError.cannotDownloadAccount
}
accountDataFromDb.lastSeenStatusId = lastSeenStatusId
try backgroundContext.save()
}
public func update(status statusData: StatusData, basedOn status: Status, for accountData: AccountData) async throws -> StatusData? {
public func update(status statusData: StatusData, basedOn status: Status, for account: AccountModel) async throws -> StatusData? {
// Load data from API and operate on CoreData on background context.
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
@ -59,17 +80,18 @@ public class HomeTimelineService {
CoreDataHandler.shared.save()
}
private func refresh(for accountData: AccountData, on backgroundContext: NSManagedObjectContext) async throws {
guard let accessToken = accountData.accessToken else {
return
private func refresh(for account: AccountModel, on backgroundContext: NSManagedObjectContext) async throws -> String? {
guard let accessToken = account.accessToken else {
return nil
}
// Retrieve statuses from API.
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
let client = MastodonClient(baseURL: account.serverUrl).getAuthenticated(token: accessToken)
let statuses = try await client.getHomeTimeline(limit: 40)
// Retrieve all statuses from database.
let dbStatuses = StatusDataHandler.shared.getAllStatuses(accountId: accountData.id)
let dbStatuses = StatusDataHandler.shared.getAllStatuses(accountId: account.id)
let lastSeenStatusId = dbStatuses.last?.rebloggedStatusId ?? dbStatuses.last?.id
// Remove statuses that are not in 40 downloaded once.
var dbStatusesToRemove: [StatusData] = []
@ -80,7 +102,7 @@ public class HomeTimelineService {
}
if !dbStatusesToRemove.isEmpty {
StatusDataHandler.shared.remove(accountId: accountData.id, statuses: dbStatusesToRemove)
StatusDataHandler.shared.remove(accountId: account.id, statuses: dbStatusesToRemove)
}
// Add statuses which are not existing in database, but has been downloaded via API.
@ -93,32 +115,35 @@ public class HomeTimelineService {
// Save statuses in database.
if !statusesToAdd.isEmpty {
_ = try await self.save(statuses: statusesToAdd, for: accountData, on: backgroundContext)
_ = try await self.save(statuses: statusesToAdd, for: account, on: backgroundContext)
}
return lastSeenStatusId
}
private func load(for accountData: AccountData,
private func load(for account: AccountModel,
on backgroundContext: NSManagedObjectContext,
minId: String? = nil,
maxId: String? = nil
) async throws -> [Status] {
guard let accessToken = accountData.accessToken else {
guard let accessToken = account.accessToken else {
return []
}
// Retrieve statuses from API.
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
let client = MastodonClient(baseURL: account.serverUrl).getAuthenticated(token: accessToken)
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 20)
// Save statuses in database.
return try await self.save(statuses: statuses, for: accountData, on: backgroundContext)
return try await self.save(statuses: statuses, for: account, on: backgroundContext)
}
private func save(statuses: [Status],
for accountData: AccountData,
for account: AccountModel,
on backgroundContext: NSManagedObjectContext
) async throws -> [Status] {
guard let dbAccount = AccountDataHandler.shared.getAccountData(accountId: accountData.id, viewContext: backgroundContext) else {
guard let accountDataFromDb = AccountDataHandler.shared.getAccountData(accountId: account.id, viewContext: backgroundContext) else {
throw DatabaseError.cannotDownloadAccount
}
@ -129,8 +154,8 @@ public class HomeTimelineService {
for status in statusesWithImages {
let statusData = StatusDataHandler.shared.createStatusDataEntity(viewContext: backgroundContext)
statusData.pixelfedAccount = dbAccount
dbAccount.addToStatuses(statusData)
statusData.pixelfedAccount = accountDataFromDb
accountDataFromDb.addToStatuses(statusData)
self.copy(from: status, to: statusData, on: backgroundContext)
}

View File

@ -11,13 +11,13 @@ public class NotificationService {
public static let shared = NotificationService()
private init() { }
public func notifications(for accountData: AccountData?,
public func notifications(for account: AccountModel?,
maxId: MaxId? = nil,
sinceId: SinceId? = nil,
minId: MinId? = nil,
limit: Int? = nil
) async throws -> Linkable<[MastodonKit.Notification]> {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return Linkable<[MastodonKit.Notification]>(data: [])
}

View File

@ -11,14 +11,14 @@ public class PublicTimelineService {
public static let shared = PublicTimelineService()
private init() { }
public func getStatuses(for accountData: AccountData?,
public func getStatuses(for account: AccountModel?,
local: Bool,
remote: Bool,
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
@ -26,7 +26,7 @@ public class PublicTimelineService {
return try await client.getPublicTimeline(local: local, remote: remote, onlyMedia: true, maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
}
public func getTagStatuses(for accountData: AccountData?,
public func getTagStatuses(for account: AccountModel?,
tag: String,
local: Bool,
remote: Bool,
@ -34,7 +34,7 @@ public class PublicTimelineService {
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}

View File

@ -19,7 +19,7 @@ enum RouteurDestinations: Hashable {
enum SheetDestinations: Identifiable {
case newStatusEditor
case replyToStatusEditor(status: StatusViewModel)
case replyToStatusEditor(status: StatusModel)
case settings
public var id: String {
@ -45,14 +45,14 @@ class RouterPath: ObservableObject {
path.append(to)
}
public func handle(url: URL, accountData: AccountData? = nil) -> OpenURLAction.Result {
public func handle(url: URL, account: AccountModel? = nil) -> OpenURLAction.Result {
if url.pathComponents.contains(where: { $0 == "tags" }), let tag = url.pathComponents.last {
navigate(to: .tag(hashTag: tag))
return .handled
} else if url.lastPathComponent.first == "@", let host = url.host {
let acct = "\(url.lastPathComponent)@\(host)"
Task {
await navigateToAccountFrom(acct: acct, url: url, accountData: accountData)
await navigateToAccountFrom(acct: acct, url: url, account: account)
}
return .handled
@ -61,16 +61,18 @@ class RouterPath: ObservableObject {
return urlHandler?(url) ?? .systemAction
}
public func navigateToAccountFrom(acct: String, url: URL, accountData: AccountData? = nil) async {
guard let accountData else { return }
public func navigateToAccountFrom(acct: String, url: URL, account: AccountModel? = nil) async {
guard let account else { return }
Task {
let results = try? await SearchService.shared.search(for: accountData,
let results = try? await SearchService.shared.search(for: account,
query: acct,
resultsType: Mastodon.Search.ResultsType.accounts)
if let account = results?.accounts.first {
navigate(to: .userProfile(accountId: account.id, accountDisplayName: account.displayNameWithoutEmojis, accountUserName: account.acct))
if let accountFromApi = results?.accounts.first {
navigate(to: .userProfile(accountId: accountFromApi.id,
accountDisplayName: accountFromApi.displayNameWithoutEmojis,
accountUserName: accountFromApi.acct))
} else {
await UIApplication.shared.open(url)
}

View File

@ -11,10 +11,10 @@ public class SearchService {
public static let shared = SearchService()
private init() { }
public func search(for accountData: AccountData?,
public func search(for account: AccountModel?,
query: String,
resultsType: Mastodon.Search.ResultsType) async throws -> SearchResults? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}

View File

@ -11,8 +11,8 @@ public class StatusService {
public static let shared = StatusService()
private init() { }
public func status(withId statusId: String, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func status(withId statusId: String, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -20,8 +20,8 @@ public class StatusService {
return try await client.status(statusId: statusId)
}
func favourite(statusId: String, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
func favourite(statusId: String, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -29,8 +29,8 @@ public class StatusService {
return try await client.favourite(statusId: statusId)
}
func unfavourite(statusId: String, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
func unfavourite(statusId: String, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -38,8 +38,8 @@ public class StatusService {
return try await client.unfavourite(statusId: statusId)
}
func boost(statusId: String, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
func boost(statusId: String, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -47,8 +47,8 @@ public class StatusService {
return try await client.boost(statusId: statusId)
}
func unboost(statusId: String, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
func unboost(statusId: String, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -56,8 +56,8 @@ public class StatusService {
return try await client.unboost(statusId: statusId)
}
func bookmark(statusId: String, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
func bookmark(statusId: String, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -65,8 +65,8 @@ public class StatusService {
return try await client.bookmark(statusId: statusId)
}
func unbookmark(statusId: String, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
func unbookmark(statusId: String, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -74,8 +74,8 @@ public class StatusService {
return try await client.unbookmark(statusId: statusId)
}
func new(status: Mastodon.Statuses.Components, for accountData: AccountData?) async throws -> Status? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
func new(status: Mastodon.Statuses.Components, for account: AccountModel?) async throws -> Status? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -83,17 +83,17 @@ public class StatusService {
return try await client.new(statusComponents: status)
}
func comments(to statusId: String, for accountData: AccountData) async throws -> [CommentViewModel] {
var commentViewModels: [CommentViewModel] = []
func comments(to statusId: String, for account: AccountModel) async throws -> [CommentModel] {
var commentViewModels: [CommentModel] = []
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? String.empty())
let client = MastodonClient(baseURL: account.serverUrl).getAuthenticated(token: account.accessToken ?? String.empty())
try await self.getCommentDescendants(to: statusId, client: client, showDivider: true, to: &commentViewModels)
return commentViewModels
}
public func favouritedBy(statusId: String, for accountData: AccountData?, page: Int) async throws -> [Account] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func favouritedBy(statusId: String, for account: AccountModel?, page: Int) async throws -> [Account] {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
@ -101,8 +101,8 @@ public class StatusService {
return try await client.favouritedBy(for: statusId, page: page)
}
public func rebloggedBy(statusId: String, for accountData: AccountData?, page: Int) async throws -> [Account] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func rebloggedBy(statusId: String, for account: AccountModel?, page: Int) async throws -> [Account] {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
@ -110,12 +110,12 @@ public class StatusService {
return try await client.rebloggedBy(for: statusId, page: page)
}
private func getCommentDescendants(to statusId: String, client: MastodonClientAuthenticated, showDivider: Bool, to commentViewModels: inout [CommentViewModel]) async throws {
private func getCommentDescendants(to statusId: String, client: MastodonClientAuthenticated, showDivider: Bool, to commentViewModels: inout [CommentModel]) async throws {
let context = try await client.getContext(for: statusId)
let descendants = context.descendants.toStatusViewModel()
for status in descendants {
commentViewModels.append(CommentViewModel(status: status, showDivider: showDivider))
commentViewModels.append(CommentModel(status: status, showDivider: showDivider))
if status.repliesCount > 0 {
try await self.getCommentDescendants(to: status.id, client: client, showDivider: false, to: &commentViewModels)

View File

@ -11,8 +11,8 @@ public class TagsService {
public static let shared = TagsService()
private init() { }
public func get(tag: String, for accountData: AccountData?) async throws -> Tag? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func get(tag: String, for account: AccountModel?) async throws -> Tag? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -20,8 +20,8 @@ public class TagsService {
return try await client.tag(hashtag: tag)
}
public func follow(tag: String, for accountData: AccountData?) async throws -> Tag? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func follow(tag: String, for account: AccountModel?) async throws -> Tag? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}
@ -29,8 +29,8 @@ public class TagsService {
return try await client.follow(hashtag: tag)
}
public func unfollow(tag: String, for accountData: AccountData?) async throws -> Tag? {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
public func unfollow(tag: String, for account: AccountModel?) async throws -> Tag? {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return nil
}

View File

@ -11,9 +11,9 @@ public class TrendsService {
public static let shared = TrendsService()
private init() { }
public func statuses(for accountData: AccountData?,
public func statuses(for account: AccountModel?,
range: Mastodon.PixelfedTrends.TrendRange) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}

View File

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Vernissage.xcdatamodel</string>
<string>Vernissage-001.xcdatamodel</string>
</dict>
</plist>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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="clientId" attributeType="String"/>
<attribute name="clientSecret" attributeType="String"/>
<attribute name="clientVapidKey" attributeType="String"/>
<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="lastSeenStatusId" optional="YES" attributeType="String"/>
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="refreshToken" optional="YES" attributeType="String"/>
<attribute name="serverUrl" attributeType="URI"/>
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="username" attributeType="String"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="StatusData" inverseName="pixelfedAccount" inverseEntity="StatusData"/>
</entity>
<entity name="ApplicationSettings" representedClassName="ApplicationSettings" syncable="YES">
<attribute name="avatarShape" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="currentAccount" optional="YES" attributeType="String"/>
<attribute name="theme" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tintColor" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
</entity>
<entity name="AttachmentData" representedClassName="AttachmentData" syncable="YES">
<attribute name="blurhash" optional="YES" attributeType="String"/>
<attribute name="data" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
<attribute name="exifCamera" optional="YES" attributeType="String"/>
<attribute name="exifCreatedDate" optional="YES" attributeType="String"/>
<attribute name="exifExposure" optional="YES" attributeType="String"/>
<attribute name="exifLens" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="metaImageHeight" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="metaImageWidth" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="previewUrl" optional="YES" attributeType="URI"/>
<attribute name="remoteUrl" optional="YES" attributeType="URI"/>
<attribute name="statusId" attributeType="String"/>
<attribute name="text" optional="YES" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="url" attributeType="URI"/>
<relationship name="statusRelation" maxCount="1" deletionRule="Nullify" destinationEntity="StatusData" inverseName="attachmentsRelation" inverseEntity="StatusData"/>
</entity>
<entity name="StatusData" representedClassName="StatusData" syncable="YES">
<attribute name="accountAvatar" optional="YES" attributeType="URI"/>
<attribute name="accountDisplayName" optional="YES" attributeType="String"/>
<attribute name="accountId" attributeType="String"/>
<attribute name="accountUsername" optional="YES" attributeType="String"/>
<attribute name="applicationName" optional="YES" attributeType="String"/>
<attribute name="applicationWebsite" optional="YES" attributeType="URI"/>
<attribute name="bookmarked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="content" attributeType="String"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="favourited" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="favouritesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="String"/>
<attribute name="inReplyToAccount" optional="YES" attributeType="String"/>
<attribute name="inReplyToId" optional="YES" attributeType="String"/>
<attribute name="muted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="pinned" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="reblogged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="rebloggedAccountAvatar" optional="YES" attributeType="URI"/>
<attribute name="rebloggedAccountDisplayName" optional="YES" attributeType="String"/>
<attribute name="rebloggedAccountId" optional="YES" attributeType="String"/>
<attribute name="rebloggedAccountUsername" optional="YES" attributeType="String"/>
<attribute name="rebloggedStatusId" optional="YES" attributeType="String"/>
<attribute name="reblogsCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="repliesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="spoilerText" optional="YES" attributeType="String"/>
<attribute name="uri" attributeType="String"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="visibility" attributeType="String"/>
<relationship name="attachmentsRelation" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AttachmentData" inverseName="statusRelation" inverseEntity="AttachmentData"/>
<relationship name="pixelfedAccount" maxCount="1" deletionRule="Nullify" destinationEntity="AccountData" inverseName="statuses" inverseEntity="AccountData"/>
</entity>
</model>

View File

@ -65,7 +65,8 @@ struct VernissageApp: App {
}
Task { @MainActor in
self.applicationState.accountData = accountData
self.applicationState.account = AccountModel(accountData: accountData)
self.applicationState.lastSeenStatusId = accountData.lastSeenStatusId
self.applicationViewMode = .mainView
}
}

View File

@ -0,0 +1,28 @@
//
// https://mczachurski.dev
// Copyright © 2023 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import Foundation
import SwiftUI
public extension View {
func onFirstAppear(_ action: @escaping () async -> ()) -> some View {
modifier(FirstAppear(action: action))
}
}
private struct FirstAppear: ViewModifier {
let action: () async -> ()
@State private var hasAppeared = false
func body(content: Content) -> some View {
content.task {
guard !hasAppeared else { return }
hasAppeared = true
await action()
}
}
}

View File

@ -70,11 +70,7 @@ struct AccountsView: View {
}
.navigationBarTitle(self.getTitle())
.listStyle(.plain)
.task {
if self.accounts.isEmpty == false {
return
}
.onFirstAppear {
await self.loadAccounts(page: self.page)
}
}
@ -113,22 +109,22 @@ struct AccountsView: View {
case .followers:
return try await AccountService.shared.followers(
account: self.entityId,
for: self.applicationState.accountData,
for: self.applicationState.account,
page: page)
case .following:
return try await AccountService.shared.following(
account: self.entityId,
for: self.applicationState.accountData,
for: self.applicationState.account,
page: page)
case .favourited:
return try await StatusService.shared.favouritedBy(
statusId: self.entityId,
for: self.applicationState.accountData,
for: self.applicationState.account,
page: page)
case .reblogged:
return try await StatusService.shared.rebloggedBy(
statusId: self.entityId,
for: self.applicationState.accountData,
for: self.applicationState.account,
page: page)
}
}

View File

@ -15,7 +15,7 @@ struct ComposeView: View {
@EnvironmentObject var applicationState: ApplicationState
@Environment(\.dismiss) private var dismiss
@State var statusViewModel: StatusViewModel?
@State var statusViewModel: StatusModel?
@State private var text = String.empty()
@FocusState private var focusedField: FocusField?
@ -26,7 +26,7 @@ struct ComposeView: View {
NavigationView {
ScrollView {
VStack (alignment: .leading){
if let accountData = applicationState.accountData {
if let accountData = applicationState.account {
HStack {
UsernameRow(
accountId: accountData.id,
@ -102,7 +102,7 @@ struct ComposeView: View {
do {
_ = try await StatusService.shared.new(
status: Mastodon.Statuses.Components(inReplyToId: self.statusViewModel?.id, text: self.text),
for: self.applicationState.accountData)
for: self.applicationState.account)
} catch {
ErrorService.shared.handle(error, message: "Error during post status.", showToastr: true)
}

View File

@ -29,6 +29,11 @@ struct HomeFeedView: View {
ScrollView {
LazyVGrid(columns: gridColumns) {
ForEach(dbStatuses, id: \.self) { item in
if self.shouldUpToDateBeVisible(statusId: item.id) {
self.upToDatePlaceholder()
}
NavigationLink(value: RouteurDestinations.status(
id: item.rebloggedStatusId ?? item.id,
blurhash: item.attachments().first?.blurhash,
@ -45,8 +50,8 @@ struct HomeFeedView: View {
LoadingIndicator()
.task {
do {
if let accountData = self.applicationState.accountData {
let newStatusesCount = try await HomeTimelineService.shared.loadOnBottom(for: accountData)
if let account = self.applicationState.account {
let newStatusesCount = try await HomeTimelineService.shared.loadOnBottom(for: account)
if newStatusesCount == 0 {
allItemsBottomLoaded = true
}
@ -75,8 +80,11 @@ struct HomeFeedView: View {
}
.refreshable {
do {
if let accountData = self.applicationState.accountData {
try await HomeTimelineService.shared.loadOnTop(for: accountData)
if let account = self.applicationState.account {
if let lastSeenStatusId = try await HomeTimelineService.shared.loadOnTop(for: account) {
try await HomeTimelineService.shared.save(lastSeenStatusId: lastSeenStatusId, for: account)
self.applicationState.lastSeenStatusId = lastSeenStatusId
}
}
} catch {
print("Error", error)
@ -94,12 +102,33 @@ struct HomeFeedView: View {
return
}
if let accountData = self.applicationState.accountData {
try await HomeTimelineService.shared.loadOnTop(for: accountData)
if let account = self.applicationState.account {
_ = try await HomeTimelineService.shared.loadOnTop(for: account)
}
} catch {
ErrorService.shared.handle(error, message: "Error during download statuses from server.", showToastr: !Task.isCancelled)
}
}
}
private func shouldUpToDateBeVisible(statusId: String) -> Bool {
return self.applicationState.lastSeenStatusId != dbStatuses.first?.id && self.applicationState.lastSeenStatusId == statusId
}
@ViewBuilder
private func upToDatePlaceholder() -> some View {
VStack(alignment: .center) {
Image(systemName: "checkmark.seal")
.resizable()
.frame(width: 64, height: 64)
.fontWeight(.ultraLight)
.foregroundColor(.accentColor.opacity(0.6))
Text("You're all caught up")
.font(.title2)
.fontWeight(.thin)
.foregroundColor(Color.mainTextColor.opacity(0.6))
}
.padding(.vertical, 8)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width * 0.75)
}
}

View File

@ -47,28 +47,28 @@ struct MainView: View {
private func getMainView() -> some View {
switch self.viewMode {
case .home:
HomeFeedView(accountId: applicationState.accountData?.id ?? String.empty())
.id(applicationState.accountData?.id ?? String.empty())
HomeFeedView(accountId: applicationState.account?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
case .trending:
TrendStatusesView(accountId: applicationState.accountData?.id ?? String.empty())
.id(applicationState.accountData?.id ?? String.empty())
TrendStatusesView(accountId: applicationState.account?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
case .local:
StatusesView(listType: .local)
.id(applicationState.accountData?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
case .federated:
StatusesView(listType: .federated)
.id(applicationState.accountData?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
case .profile:
if let accountData = self.applicationState.accountData {
if let accountData = self.applicationState.account {
UserProfileView(accountId: accountData.id,
accountDisplayName: accountData.displayName,
accountUserName: accountData.acct)
.id(applicationState.accountData?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
}
case .notifications:
if let accountData = self.applicationState.accountData {
if let accountData = self.applicationState.account {
NotificationsView(accountId: accountData.id)
.id(applicationState.accountData?.id ?? String.empty())
.id(applicationState.account?.id ?? String.empty())
}
}
}
@ -151,11 +151,12 @@ struct MainView: View {
Menu {
ForEach(self.dbAccounts) { account in
Button {
self.applicationState.accountData = account
self.applicationState.account = AccountModel(accountData: account)
self.applicationState.lastSeenStatusId = account.lastSeenStatusId
ApplicationSettingsHandler.shared.setAccountAsDefault(accountData: account)
} label: {
if self.applicationState.accountData?.id == account.id {
if self.applicationState.account?.id == account.id {
Label(account.displayName ?? account.acct, systemImage: "checkmark")
} else {
Text(account.displayName ?? account.acct)
@ -171,7 +172,7 @@ struct MainView: View {
Label("Settings", systemImage: "gear")
}
} label: {
if let avatarData = self.applicationState.accountData?.avatarData, let uiImage = UIImage(data: avatarData) {
if let avatarData = self.applicationState.account?.avatarData, let uiImage = UIImage(data: avatarData) {
Image(uiImage: uiImage)
.resizable()
.clipShape(self.applicationState.avatarShape.shape())

View File

@ -57,11 +57,7 @@ struct NotificationsView: View {
.refreshable {
await self.loadNewNotifications()
}
.task {
if self.notifications.isEmpty == false {
return
}
.onFirstAppear {
await self.loadNotifications()
}
}
@ -69,7 +65,7 @@ struct NotificationsView: View {
func loadNotifications() async {
do {
let linkable = try await NotificationService.shared.notifications(
for: self.applicationState.accountData,
for: self.applicationState.account,
maxId: maxId,
minId: minId,
limit: 5)
@ -91,7 +87,7 @@ struct NotificationsView: View {
private func loadMoreNotifications() async {
do {
let linkable = try await NotificationService.shared.notifications(
for: self.applicationState.accountData,
for: self.applicationState.account,
maxId: self.maxId,
limit: self.defaultPageSize)
@ -109,7 +105,7 @@ struct NotificationsView: View {
private func loadNewNotifications() async {
do {
let linkable = try await NotificationService.shared.notifications(
for: self.applicationState.accountData,
for: self.applicationState.account,
minId: self.minId,
limit: self.defaultPageSize)

View File

@ -70,7 +70,7 @@ struct SignInView: View {
try await AuthorizationService.shared.sign(in: self.getServerAddress(),
session: authorizationSession) { accountData in
DispatchQueue.main.async {
self.applicationState.accountData = accountData
self.applicationState.account = AccountModel(accountData: accountData)
onSignInStateChenge?(.mainView)
dismiss()
}

View File

@ -23,7 +23,7 @@ struct StatusView: View {
@State private var showImageViewer = false
@State private var firstLoadFinished = false
@State private var statusViewModel: StatusViewModel?
@State private var statusViewModel: StatusModel?
@State private var selectedAttachmentId: String?
@State private var exifCamera: String?
@ -62,7 +62,7 @@ struct StatusView: View {
MarkdownFormattedText(statusViewModel.content.asMarkdown)
.environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url, accountData: self.applicationState.accountData)
routerPath.handle(url: url, account: self.applicationState.account)
})
VStack (alignment: .leading) {
@ -114,21 +114,21 @@ struct StatusView: View {
}
// Get status from API.
if let status = try await StatusService.shared.status(withId: self.statusId, for: self.applicationState.accountData) {
let statusViewModel = StatusViewModel(status: status)
if let status = try await StatusService.shared.status(withId: self.statusId, for: self.applicationState.account) {
let statusViewModel = StatusModel(status: status)
self.statusViewModel = statusViewModel
self.selectedAttachmentId = statusViewModel.mediaAttachments.first?.id ?? String.empty()
self.firstLoadFinished = true
// If we have status in database then we can update data.
if let accountData = self.applicationState.accountData,
if let accountData = self.applicationState.account,
let statusDataFromDatabase = StatusDataHandler.shared.getStatusData(accountId: accountData.id, statusId: self.statusId) {
_ = try await HomeTimelineService.shared.update(status: statusDataFromDatabase, basedOn: status, for: accountData)
}
}
} catch NetworkError.notSuccessResponse(let response) {
if response.statusCode() == HTTPStatusCode.notFound, let accountId = self.applicationState.accountData?.id {
if response.statusCode() == HTTPStatusCode.notFound, let accountId = self.applicationState.account?.id {
StatusDataHandler.shared.remove(accountId: accountId, statusId: self.statusId)
ErrorService.shared.handle(NetworkError.notSuccessResponse(response), message: "Status not existing anymore.", showToastr: true)
dismiss()

View File

@ -25,7 +25,7 @@ struct StatusesView: View {
@State private var firstLoadFinished = false
@State private var tag: Tag?
@State private var statusViewModels: [StatusViewModel] = []
@State private var statusViewModels: [StatusModel] = []
private let defaultLimit = 20
var body: some View {
@ -82,7 +82,7 @@ struct StatusesView: View {
// TODO: It seems like pixelfed is not supporting the endpoints.
// self.getTrailingToolbar()
}
.task {
.onFirstAppear {
do {
try await self.loadStatuses()
@ -107,10 +107,10 @@ struct StatusesView: View {
}
let statuses = try await self.loadFromApi()
var inPlaceStatuses: [StatusViewModel] = []
var inPlaceStatuses: [StatusModel] = []
for item in statuses.getStatusesWithImagesOnly() {
inPlaceStatuses.append(StatusViewModel(status: item))
inPlaceStatuses.append(StatusModel(status: item))
}
self.firstLoadFinished = true
@ -129,9 +129,9 @@ struct StatusesView: View {
self.allItemsLoaded = true
}
var inPlaceStatuses: [StatusViewModel] = []
var inPlaceStatuses: [StatusModel] = []
for item in previousStatuses.getStatusesWithImagesOnly() {
inPlaceStatuses.append(StatusViewModel(status: item))
inPlaceStatuses.append(StatusModel(status: item))
}
self.statusViewModels.append(contentsOf: inPlaceStatuses)
@ -142,9 +142,9 @@ struct StatusesView: View {
if let firstStatusId = self.statusViewModels.first?.id {
let newestStatuses = try await self.loadFromApi(sinceId: firstStatusId)
var inPlaceStatuses: [StatusViewModel] = []
var inPlaceStatuses: [StatusModel] = []
for item in newestStatuses.getStatusesWithImagesOnly() {
inPlaceStatuses.append(StatusViewModel(status: item))
inPlaceStatuses.append(StatusModel(status: item))
}
self.statusViewModels.insert(contentsOf: inPlaceStatuses, at: 0)
@ -155,7 +155,7 @@ struct StatusesView: View {
switch self.listType {
case .local:
return try await PublicTimelineService.shared.getStatuses(
for: self.applicationState.accountData,
for: self.applicationState.account,
local: true,
remote: false,
maxId: maxId,
@ -164,7 +164,7 @@ struct StatusesView: View {
limit: self.defaultLimit)
case .federated:
return try await PublicTimelineService.shared.getStatuses(
for: self.applicationState.accountData,
for: self.applicationState.account,
local: false,
remote: true,
maxId: maxId,
@ -173,21 +173,21 @@ struct StatusesView: View {
limit: self.defaultLimit)
case .favourites:
return try await AccountService.shared.favourites(
for: self.applicationState.accountData,
for: self.applicationState.account,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit)
case .bookmarks:
return try await AccountService.shared.bookmarks(
for: self.applicationState.accountData,
for: self.applicationState.account,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit)
case .hashtag(let tag):
return try await PublicTimelineService.shared.getTagStatuses(
for: self.applicationState.accountData,
for: self.applicationState.account,
tag: tag,
local: false,
remote: true,
@ -235,7 +235,7 @@ struct StatusesView: View {
private func loadTag(hashtag: String) async {
do {
self.tag = try await TagsService.shared.get(tag: hashtag, for: self.applicationState.accountData)
self.tag = try await TagsService.shared.get(tag: hashtag, for: self.applicationState.account)
} catch {
ErrorService.shared.handle(error, message: "Error during loading tag from server.", showToastr: false)
}
@ -243,7 +243,7 @@ struct StatusesView: View {
private func follow(hashtag: String) async {
do {
self.tag = try await TagsService.shared.follow(tag: hashtag, for: self.applicationState.accountData)
self.tag = try await TagsService.shared.follow(tag: hashtag, for: self.applicationState.account)
ToastrService.shared.showSuccess("You are following the tag.", imageSystemName: "number.square.fill")
} catch {
ErrorService.shared.handle(error, message: "Error during following tag.", showToastr: true)
@ -252,7 +252,7 @@ struct StatusesView: View {
private func unfollow(hashtag: String) async {
do {
self.tag = try await TagsService.shared.unfollow(tag: hashtag, for: self.applicationState.accountData)
self.tag = try await TagsService.shared.unfollow(tag: hashtag, for: self.applicationState.account)
ToastrService.shared.showSuccess("Tag has been unfollowed.", imageSystemName: "number.square")
} catch {
ErrorService.shared.handle(error, message: "Error during unfollowing tag.", showToastr: true)

View File

@ -15,7 +15,7 @@ struct TrendStatusesView: View {
@State private var firstLoadFinished = false
@State private var tabSelectedValue: Mastodon.PixelfedTrends.TrendRange = .daily
@State private var statusViewModels: [StatusViewModel] = []
@State private var statusViewModels: [StatusModel] = []
var body: some View {
ScrollView {
@ -73,7 +73,7 @@ struct TrendStatusesView: View {
}
}
}
.task {
.onFirstAppear {
do {
try await self.loadStatuses()
} catch {
@ -94,13 +94,13 @@ struct TrendStatusesView: View {
}
let statuses = try await TrendsService.shared.statuses(
for: self.applicationState.accountData,
for: self.applicationState.account,
range: tabSelectedValue)
var inPlaceStatuses: [StatusViewModel] = []
var inPlaceStatuses: [StatusModel] = []
for item in statuses.getStatusesWithImagesOnly() {
inPlaceStatuses.append(StatusViewModel(status: item))
inPlaceStatuses.append(StatusModel(status: item))
}
self.statusViewModels = inPlaceStatuses

View File

@ -33,7 +33,7 @@ struct UserProfileView: View {
.navigationBarTitle(self.accountDisplayName ?? self.accountUserName)
.toolbar {
if let account = self.account {
if self.applicationState.accountData?.id != account.id {
if self.applicationState.account?.id != account.id {
self.getTrailingAccountToolbar(account: account)
} else {
self.getTrailingProfileToolbar(account: account)
@ -54,8 +54,8 @@ struct UserProfileView: View {
return
}
async let relationshipTask = AccountService.shared.relationships(withId: self.accountId, for: self.applicationState.accountData)
async let accountTask = AccountService.shared.account(withId: self.accountId, for: self.applicationState.accountData)
async let relationshipTask = AccountService.shared.relationships(withId: self.accountId, for: self.applicationState.account)
async let accountTask = AccountService.shared.account(withId: self.accountId, for: self.applicationState.account)
// Wait for download account and relationships.
self.firstLoadFinished = true
@ -148,14 +148,14 @@ struct UserProfileView: View {
if self.relationship?.muting == true {
if let relationship = try await AccountService.shared.unmute(
account: account.id,
for: self.applicationState.accountData
for: self.applicationState.account
) {
self.relationship = relationship
}
} else {
if let relationship = try await AccountService.shared.mute(
account: account.id,
for: self.applicationState.accountData
for: self.applicationState.account
) {
self.relationship = relationship
}
@ -170,14 +170,14 @@ struct UserProfileView: View {
if self.relationship?.blocking == true {
if let relationship = try await AccountService.shared.unblock(
account: account.id,
for: self.applicationState.accountData
for: self.applicationState.account
) {
self.relationship = relationship
}
} else {
if let relationship = try await AccountService.shared.block(
account: account.id,
for: self.applicationState.accountData
for: self.applicationState.account
) {
self.relationship = relationship
}

View File

@ -7,11 +7,11 @@
import SwiftUI
struct ImageCarouselPicture: View {
@ObservedObject public var attachment: AttachmentViewModel
@ObservedObject public var attachment: AttachmentModel
private let onImageDownloaded: (AttachmentViewModel, Data) -> Void
private let onImageDownloaded: (AttachmentModel, Data) -> Void
init(attachment: AttachmentViewModel, onImageDownloaded: @escaping (_: AttachmentViewModel, _: Data) -> Void) {
init(attachment: AttachmentModel, onImageDownloaded: @escaping (_: AttachmentModel, _: Data) -> Void) {
self.attachment = attachment
self.onImageDownloaded = onImageDownloaded
}

View File

@ -9,13 +9,13 @@ import MastodonKit
import NukeUI
struct ImageRowAsync: View {
@State public var statusViewModel: StatusViewModel
@State public var statusViewModel: StatusModel
@State private var imageHeight: Double
@State private var imageWidth: Double
@State private var heightWasPrecalculated: Bool
init(statusViewModel: StatusViewModel) {
init(statusViewModel: StatusModel) {
self.statusViewModel = statusViewModel
// Calculate size of frame (first from cache, then from metadata).

View File

@ -8,7 +8,7 @@ import SwiftUI
import MastodonKit
struct ImagesCarousel: View {
@State public var attachments: [AttachmentViewModel]
@State public var attachments: [AttachmentModel]
@State private var imageHeight: Double
@State private var imageWidth: Double
@State private var selected: String
@ -20,7 +20,7 @@ struct ImagesCarousel: View {
@Binding public var exifCreatedDate: String?
@Binding public var exifLens: String?
init(attachments: [AttachmentViewModel],
init(attachments: [AttachmentModel],
selectedAttachmentId: Binding<String?>,
exifCamera: Binding<String?>,
exifExposure: Binding<String?>,
@ -92,7 +92,7 @@ struct ImagesCarousel: View {
}
}
private func recalculateImageHeight(attachment: AttachmentViewModel, imageData: Data) {
private func recalculateImageHeight(attachment: AttachmentModel, imageData: Data) {
guard heightWasPrecalculated == false else {
return
}

View File

@ -7,7 +7,7 @@
import SwiftUI
struct ImagesViewer: View {
@State var statusViewModel: StatusViewModel
@State var statusViewModel: StatusModel
@State var selectedAttachmentId: String = String.empty()
@Environment(\.dismiss) private var dismiss

View File

@ -12,7 +12,7 @@ struct InteractionRow: View {
@EnvironmentObject var applicationState: ApplicationState
@EnvironmentObject var routerPath: RouterPath
@State var statusViewModel: StatusViewModel
@State var statusViewModel: StatusModel
@State private var repliesCount = 0
@State private var reblogged = false
@ -38,8 +38,8 @@ struct InteractionRow: View {
ActionButton {
do {
let status = self.reblogged
? try await StatusService.shared.unboost(statusId: self.statusViewModel.id, for: self.applicationState.accountData)
: try await StatusService.shared.boost(statusId: self.statusViewModel.id, for: self.applicationState.accountData)
? try await StatusService.shared.unboost(statusId: self.statusViewModel.id, for: self.applicationState.account)
: try await StatusService.shared.boost(statusId: self.statusViewModel.id, for: self.applicationState.account)
if let status {
self.reblogsCount = status.reblogsCount == self.reblogsCount
@ -66,8 +66,8 @@ struct InteractionRow: View {
ActionButton {
do {
let status = self.favourited
? try await StatusService.shared.unfavourite(statusId: self.statusViewModel.id, for: self.applicationState.accountData)
: try await StatusService.shared.favourite(statusId: self.statusViewModel.id, for: self.applicationState.accountData)
? try await StatusService.shared.unfavourite(statusId: self.statusViewModel.id, for: self.applicationState.account)
: try await StatusService.shared.favourite(statusId: self.statusViewModel.id, for: self.applicationState.account)
if let status {
self.favouritesCount = status.favouritesCount == self.favouritesCount
@ -94,8 +94,8 @@ struct InteractionRow: View {
ActionButton {
do {
_ = self.bookmarked
? try await StatusService.shared.unbookmark(statusId: self.statusViewModel.id, for: self.applicationState.accountData)
: try await StatusService.shared.bookmark(statusId: self.statusViewModel.id, for: self.applicationState.accountData)
? try await StatusService.shared.unbookmark(statusId: self.statusViewModel.id, for: self.applicationState.account)
: try await StatusService.shared.bookmark(statusId: self.statusViewModel.id, for: self.applicationState.account)
self.bookmarked.toggle()
ToastrService.shared.showSuccess("Bookmarked", imageSystemName: "bookmark.fill")

View File

@ -106,7 +106,7 @@ struct NotificationRow: View {
switch notification.type {
case .favourite, .reblog, .mention, .status, .poll, .update:
if let status = notification.status {
let statusViewModel = StatusViewModel(status: status)
let statusViewModel = StatusModel(status: status)
self.routerPath.navigate(to: .status(id: statusViewModel.id,
blurhash: statusViewModel.mediaAttachments.first?.blurhash,
highestImageUrl: statusViewModel.mediaAttachments.getHighestImage()?.url,

View File

@ -20,12 +20,12 @@ struct AccountsSection: View {
accountDisplayName: account.displayName,
accountUsername: account.username)
Spacer()
if self.applicationState.accountData?.id == account.id {
if self.applicationState.account?.id == account.id {
Image(systemName: "checkmark")
.foregroundColor(self.applicationState.tintColor.color())
}
}
.deleteDisabled(self.applicationState.accountData?.id == account.id)
.deleteDisabled(self.applicationState.account?.id == account.id)
}
.onDelete(perform: delete)

View File

@ -11,7 +11,7 @@ struct CommentBody: View {
@EnvironmentObject var applicationState: ApplicationState
@EnvironmentObject var routerPath: RouterPath
@State var statusViewModel: StatusViewModel
@State var statusViewModel: StatusModel
private let contentWidth = Int(UIScreen.main.bounds.width) - 60
var body: some View {
@ -85,7 +85,7 @@ struct CommentBody: View {
.background(self.getSelectedRowColor(statusViewModel: statusViewModel))
}
private func getSelectedRowColor(statusViewModel: StatusViewModel) -> Color {
private func getSelectedRowColor(statusViewModel: StatusModel) -> Color {
return self.applicationState.showInteractionStatusId == statusViewModel.id ? Color.selectedRowColor : Color.systemBackground
}
}

View File

@ -12,7 +12,7 @@ struct CommentsSection: View {
@EnvironmentObject var applicationState: ApplicationState
@State public var statusId: String
@State private var commentViewModels: [CommentViewModel]?
@State private var commentViewModels: [CommentModel]?
var body: some View {
VStack(alignment: .leading, spacing: 0) {
@ -51,7 +51,7 @@ struct CommentsSection: View {
}
.task {
do {
if let accountData = applicationState.accountData {
if let accountData = applicationState.account {
self.commentViewModels = try await StatusService.shared.comments(to: statusId, for: accountData)
}
} catch {

View File

@ -67,7 +67,7 @@ struct UserProfileHeader: View {
Spacer()
if self.applicationState.accountData?.id != self.account.id {
if self.applicationState.account?.id != self.account.id {
self.otherAccountActionButtons()
}
}
@ -75,7 +75,7 @@ struct UserProfileHeader: View {
if let note = account.note, !note.isEmpty {
MarkdownFormattedText(note.asMarkdown, withFontSize: 14, andWidth: Int(UIScreen.main.bounds.width) - 16)
.environment(\.openURL, OpenURLAction { url in
routerPath.handle(url: url, accountData: self.applicationState.accountData)
routerPath.handle(url: url, account: self.applicationState.account)
})
}
@ -106,14 +106,14 @@ struct UserProfileHeader: View {
if self.relationship?.following == true {
if let relationship = try await AccountService.shared.unfollow(
account: self.account.id,
for: self.applicationState.accountData
for: self.applicationState.account
) {
self.relationship = relationship
}
} else {
if let relationship = try await AccountService.shared.follow(
account: self.account.id,
for: self.applicationState.accountData
for: self.applicationState.account
) {
self.relationship = relationship
}

View File

@ -14,7 +14,7 @@ struct UserProfileStatuses: View {
@State private var allItemsLoaded = false
@State private var firstLoadFinished = false
@State private var statusViewModels: [StatusViewModel] = []
@State private var statusViewModels: [StatusModel] = []
private let defaultLimit = 20
var body: some View {
@ -66,12 +66,12 @@ struct UserProfileStatuses: View {
let statuses = try await AccountService.shared.statuses(
createdBy: self.accountId,
for: self.applicationState.accountData,
for: self.applicationState.account,
limit: self.defaultLimit)
var inPlaceStatuses: [StatusViewModel] = []
var inPlaceStatuses: [StatusModel] = []
for item in statuses {
inPlaceStatuses.append(StatusViewModel(status: item))
inPlaceStatuses.append(StatusModel(status: item))
}
self.firstLoadFinished = true
@ -86,7 +86,7 @@ struct UserProfileStatuses: View {
if let lastStatusId = self.statusViewModels.last?.id {
let previousStatuses = try await AccountService.shared.statuses(
createdBy: self.accountId,
for: self.applicationState.accountData,
for: self.applicationState.account,
maxId: lastStatusId,
limit: self.defaultLimit)
@ -94,9 +94,9 @@ struct UserProfileStatuses: View {
self.allItemsLoaded = true
}
var inPlaceStatuses: [StatusViewModel] = []
var inPlaceStatuses: [StatusModel] = []
for item in previousStatuses {
inPlaceStatuses.append(StatusViewModel(status: item))
inPlaceStatuses.append(StatusModel(status: item))
}
self.statusViewModels.append(contentsOf: inPlaceStatuses)