From da3db13bc497ec1b5d891ebca8db9efd9b2b2a9d Mon Sep 17 00:00:00 2001 From: Marcin Czachursk Date: Tue, 31 Jan 2023 12:20:49 +0100 Subject: [PATCH] Add information about last seen status. --- Vernissage.xcodeproj/project.pbxproj | 52 +++++++----- .../AccountData+CoreDataProperties.swift | 2 +- Vernissage/Models/AccountModel.swift | 55 ++++++++++++ Vernissage/Models/ApplicationState.swift | 4 +- .../AttachmentModel.swift} | 6 +- .../CommentModel.swift} | 4 +- .../StatusModel.swift} | 16 ++-- Vernissage/Services/AccountService.swift | 52 ++++++------ .../Services/AuthorizationService.swift | 2 +- Vernissage/Services/HomeTimelineService.swift | 69 ++++++++++----- Vernissage/Services/NotificationService.swift | 4 +- .../Services/PublicTimelineService.swift | 8 +- Vernissage/Services/RouterPath.swift | 18 ++-- Vernissage/Services/SearchService.swift | 4 +- Vernissage/Services/StatusService.swift | 50 +++++------ Vernissage/Services/TagsService.swift | 12 +-- Vernissage/Services/TrendsService.swift | 4 +- .../Vernissage.xcdatamodeld/.xccurrentversion | 2 +- .../Vernissage-001.xcdatamodel/contents | 84 +++++++++++++++++++ Vernissage/VernissageApp.swift | 3 +- Vernissage/ViewModifiers/FirstAppear.swift | 28 +++++++ Vernissage/Views/AccountsView.swift | 14 ++-- Vernissage/Views/ComposeView.swift | 6 +- Vernissage/Views/HomeFeedView.swift | 41 +++++++-- Vernissage/Views/MainView.swift | 27 +++--- Vernissage/Views/NotificationsView.swift | 12 +-- Vernissage/Views/SignInView.swift | 2 +- Vernissage/Views/StatusView.swift | 12 +-- Vernissage/Views/StatusesView.swift | 32 +++---- Vernissage/Views/TrendStatusesView.swift | 10 +-- Vernissage/Views/UserProfileView.swift | 14 ++-- Vernissage/Widgets/ImageCarouselPicture.swift | 6 +- Vernissage/Widgets/ImageRowAsync.swift | 4 +- Vernissage/Widgets/ImagesCarousel.swift | 6 +- Vernissage/Widgets/ImagesViewer.swift | 2 +- Vernissage/Widgets/InteractionRow.swift | 14 ++-- .../NotificationsView/NotificationRow.swift | 2 +- .../SettingsView/AccountsSection.swift | 4 +- .../Widgets/StatusView/CommentBody.swift | 4 +- .../Widgets/StatusView/CommentsSection.swift | 4 +- .../UserProfile/UserProfileHeader.swift | 8 +- .../UserProfile/UserProfileStatuses.swift | 14 ++-- 42 files changed, 473 insertions(+), 244 deletions(-) create mode 100644 Vernissage/Models/AccountModel.swift rename Vernissage/{ViewModels/AttachmentViewModel.swift => Models/AttachmentModel.swift} (96%) rename Vernissage/{ViewModels/CommentViewModel.swift => Models/CommentModel.swift} (75%) rename Vernissage/{ViewModels/StatusViewModel.swift => Models/StatusModel.swift} (92%) create mode 100644 Vernissage/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents create mode 100644 Vernissage/ViewModifiers/FirstAppear.swift diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 7cf51fe..34f320c 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -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 = ""; }; F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Blurhash.swift"; sourceTree = ""; }; F898DE6F2972868A004B4A6A /* String+Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Empty.swift"; sourceTree = ""; }; - F898DE7129728CB2004B4A6A /* CommentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentViewModel.swift; sourceTree = ""; }; + F898DE7129728CB2004B4A6A /* CommentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentModel.swift; sourceTree = ""; }; F8996DEA2971D29D0043EEC6 /* View+Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Transition.swift"; sourceTree = ""; }; F89992C8296D6DC7005994BF /* CommentBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentBody.swift; sourceTree = ""; }; - F89992CB296D9231005994BF /* StatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusViewModel.swift; sourceTree = ""; }; - F89992CD296D92E7005994BF /* AttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewModel.swift; sourceTree = ""; }; + F89992CB296D9231005994BF /* StatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusModel.swift; sourceTree = ""; }; + F89992CD296D92E7005994BF /* AttachmentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentModel.swift; sourceTree = ""; }; F89A46DB296EAACE0062125F /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; F89A46DD296EABA20062125F /* StatusPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPlaceholder.swift; sourceTree = ""; }; F89CEB7F2984198600A1376F /* AttachmentData+HighestImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+HighestImage.swift"; sourceTree = ""; }; @@ -228,7 +230,10 @@ F8B1E6502973FB7E00EE0D10 /* ToastrService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastrService.swift; sourceTree = ""; }; F8C14391296AF0B3001FE31D /* String+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Exif.swift"; sourceTree = ""; }; F8C14393296AF21B001FE31D /* Double+Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Round.swift"; sourceTree = ""; }; + F8C5E55E2988E92600ADF6A7 /* AccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountModel.swift; sourceTree = ""; }; + F8C5E56129892CC300ADF6A7 /* FirstAppear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAppear.swift; sourceTree = ""; }; F8C7EDBE298169EE002843BC /* TagsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsService.swift; sourceTree = ""; }; + F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-001.xcdatamodel"; sourceTree = ""; }; F8CC95CD2970761D00C9C2AC /* TintColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColor.swift; sourceTree = ""; }; /* 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 = ""; }; - F89992CA296D9211005994BF /* ViewModels */ = { - isa = PBXGroup; - children = ( - F89992CB296D9231005994BF /* StatusViewModel.swift */, - F89992CD296D92E7005994BF /* AttachmentViewModel.swift */, - F898DE7129728CB2004B4A6A /* CommentViewModel.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; F89D6C4029717FC0001DA3D4 /* SettingsView */ = { isa = PBXGroup; children = ( @@ -525,6 +524,14 @@ path = StatusView; sourceTree = ""; }; + F8C5E56029892C8A00ADF6A7 /* ViewModifiers */ = { + isa = PBXGroup; + children = ( + F8C5E56129892CC300ADF6A7 /* FirstAppear.swift */, + ); + path = ViewModifiers; + sourceTree = ""; + }; /* 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 = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Vernissage/CoreData/AccountData+CoreDataProperties.swift b/Vernissage/CoreData/AccountData+CoreDataProperties.swift index 213cdad..77fc90f 100644 --- a/Vernissage/CoreData/AccountData+CoreDataProperties.swift +++ b/Vernissage/CoreData/AccountData+CoreDataProperties.swift @@ -34,7 +34,7 @@ extension AccountData { @NSManaged public var url: URL? @NSManaged public var username: String @NSManaged public var statuses: Set? - + @NSManaged public var lastSeenStatusId: String? } // MARK: Generated accessors for statuses diff --git a/Vernissage/Models/AccountModel.swift b/Vernissage/Models/AccountModel.swift new file mode 100644 index 0000000..56e4448 --- /dev/null +++ b/Vernissage/Models/AccountModel.swift @@ -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 + } +} diff --git a/Vernissage/Models/ApplicationState.swift b/Vernissage/Models/ApplicationState.swift index a762e3d..5b6ece0 100644 --- a/Vernissage/Models/ApplicationState.swift +++ b/Vernissage/Models/ApplicationState.swift @@ -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 diff --git a/Vernissage/ViewModels/AttachmentViewModel.swift b/Vernissage/Models/AttachmentModel.swift similarity index 96% rename from Vernissage/ViewModels/AttachmentViewModel.swift rename to Vernissage/Models/AttachmentModel.swift index b79c5d9..fda32ac 100644 --- a/Vernissage/ViewModels/AttachmentViewModel.swift +++ b/Vernissage/Models/AttachmentModel.swift @@ -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 diff --git a/Vernissage/ViewModels/CommentViewModel.swift b/Vernissage/Models/CommentModel.swift similarity index 75% rename from Vernissage/ViewModels/CommentViewModel.swift rename to Vernissage/Models/CommentModel.swift index bac3b06..9faf6ed 100644 --- a/Vernissage/ViewModels/CommentViewModel.swift +++ b/Vernissage/Models/CommentModel.swift @@ -6,7 +6,7 @@ import Foundation -public struct CommentViewModel { - var status: StatusViewModel +public struct CommentModel { + var status: StatusModel var showDivider: Bool } diff --git a/Vernissage/ViewModels/StatusViewModel.swift b/Vernissage/Models/StatusModel.swift similarity index 92% rename from Vernissage/ViewModels/StatusViewModel.swift rename to Vernissage/Models/StatusModel.swift index 08b8fd8..56d1909 100644 --- a/Vernissage/ViewModels/StatusViewModel.swift +++ b/Vernissage/Models/StatusModel.swift @@ -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) } } } diff --git a/Vernissage/Services/AccountService.swift b/Vernissage/Services/AccountService.swift index 1f814e9..cb5ecf4 100644 --- a/Vernissage/Services/AccountService.swift +++ b/Vernissage/Services/AccountService.swift @@ -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 [] } diff --git a/Vernissage/Services/AuthorizationService.swift b/Vernissage/Services/AuthorizationService.swift index 04fd7b1..262c060 100644 --- a/Vernissage/Services/AuthorizationService.swift +++ b/Vernissage/Services/AuthorizationService.swift @@ -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 diff --git a/Vernissage/Services/HomeTimelineService.swift b/Vernissage/Services/HomeTimelineService.swift index 12c15d3..51097e1 100644 --- a/Vernissage/Services/HomeTimelineService.swift +++ b/Vernissage/Services/HomeTimelineService.swift @@ -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) } diff --git a/Vernissage/Services/NotificationService.swift b/Vernissage/Services/NotificationService.swift index 0d61504..aed71af 100644 --- a/Vernissage/Services/NotificationService.swift +++ b/Vernissage/Services/NotificationService.swift @@ -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: []) } diff --git a/Vernissage/Services/PublicTimelineService.swift b/Vernissage/Services/PublicTimelineService.swift index cfadb02..68fb6cc 100644 --- a/Vernissage/Services/PublicTimelineService.swift +++ b/Vernissage/Services/PublicTimelineService.swift @@ -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 [] } diff --git a/Vernissage/Services/RouterPath.swift b/Vernissage/Services/RouterPath.swift index a04b21b..9abc273 100644 --- a/Vernissage/Services/RouterPath.swift +++ b/Vernissage/Services/RouterPath.swift @@ -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) } diff --git a/Vernissage/Services/SearchService.swift b/Vernissage/Services/SearchService.swift index bb84d3a..87c8255 100644 --- a/Vernissage/Services/SearchService.swift +++ b/Vernissage/Services/SearchService.swift @@ -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 } diff --git a/Vernissage/Services/StatusService.swift b/Vernissage/Services/StatusService.swift index 20582c4..7d87075 100644 --- a/Vernissage/Services/StatusService.swift +++ b/Vernissage/Services/StatusService.swift @@ -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) diff --git a/Vernissage/Services/TagsService.swift b/Vernissage/Services/TagsService.swift index 9a75d41..d24da9f 100644 --- a/Vernissage/Services/TagsService.swift +++ b/Vernissage/Services/TagsService.swift @@ -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 } diff --git a/Vernissage/Services/TrendsService.swift b/Vernissage/Services/TrendsService.swift index a705dad..8b766c0 100644 --- a/Vernissage/Services/TrendsService.swift +++ b/Vernissage/Services/TrendsService.swift @@ -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 [] } diff --git a/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion b/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion index b9f37ac..ad1d243 100644 --- a/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion +++ b/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Vernissage.xcdatamodel + Vernissage-001.xcdatamodel diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents b/Vernissage/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents new file mode 100644 index 0000000..5fa8504 --- /dev/null +++ b/Vernissage/Vernissage.xcdatamodeld/Vernissage-001.xcdatamodel/contents @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Vernissage/VernissageApp.swift b/Vernissage/VernissageApp.swift index fc9e40e..047c0ef 100644 --- a/Vernissage/VernissageApp.swift +++ b/Vernissage/VernissageApp.swift @@ -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 } } diff --git a/Vernissage/ViewModifiers/FirstAppear.swift b/Vernissage/ViewModifiers/FirstAppear.swift new file mode 100644 index 0000000..1038c66 --- /dev/null +++ b/Vernissage/ViewModifiers/FirstAppear.swift @@ -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() + } + } +} diff --git a/Vernissage/Views/AccountsView.swift b/Vernissage/Views/AccountsView.swift index 66126cc..80eae59 100644 --- a/Vernissage/Views/AccountsView.swift +++ b/Vernissage/Views/AccountsView.swift @@ -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) } } diff --git a/Vernissage/Views/ComposeView.swift b/Vernissage/Views/ComposeView.swift index 41c2f79..7ae68af 100644 --- a/Vernissage/Views/ComposeView.swift +++ b/Vernissage/Views/ComposeView.swift @@ -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) } diff --git a/Vernissage/Views/HomeFeedView.swift b/Vernissage/Views/HomeFeedView.swift index d6f668f..327fda0 100644 --- a/Vernissage/Views/HomeFeedView.swift +++ b/Vernissage/Views/HomeFeedView.swift @@ -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) + } } diff --git a/Vernissage/Views/MainView.swift b/Vernissage/Views/MainView.swift index 7d9c38e..67ccf8b 100644 --- a/Vernissage/Views/MainView.swift +++ b/Vernissage/Views/MainView.swift @@ -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()) diff --git a/Vernissage/Views/NotificationsView.swift b/Vernissage/Views/NotificationsView.swift index 35731a0..291a01b 100644 --- a/Vernissage/Views/NotificationsView.swift +++ b/Vernissage/Views/NotificationsView.swift @@ -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) diff --git a/Vernissage/Views/SignInView.swift b/Vernissage/Views/SignInView.swift index 204a72d..2d4ce09 100644 --- a/Vernissage/Views/SignInView.swift +++ b/Vernissage/Views/SignInView.swift @@ -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() } diff --git a/Vernissage/Views/StatusView.swift b/Vernissage/Views/StatusView.swift index 8512708..164d79f 100644 --- a/Vernissage/Views/StatusView.swift +++ b/Vernissage/Views/StatusView.swift @@ -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() diff --git a/Vernissage/Views/StatusesView.swift b/Vernissage/Views/StatusesView.swift index 2501445..5197843 100644 --- a/Vernissage/Views/StatusesView.swift +++ b/Vernissage/Views/StatusesView.swift @@ -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) diff --git a/Vernissage/Views/TrendStatusesView.swift b/Vernissage/Views/TrendStatusesView.swift index f5e8a06..f1198aa 100644 --- a/Vernissage/Views/TrendStatusesView.swift +++ b/Vernissage/Views/TrendStatusesView.swift @@ -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 diff --git a/Vernissage/Views/UserProfileView.swift b/Vernissage/Views/UserProfileView.swift index 245b012..2262e7e 100644 --- a/Vernissage/Views/UserProfileView.swift +++ b/Vernissage/Views/UserProfileView.swift @@ -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 } diff --git a/Vernissage/Widgets/ImageCarouselPicture.swift b/Vernissage/Widgets/ImageCarouselPicture.swift index 55bc6ec..317f7f6 100644 --- a/Vernissage/Widgets/ImageCarouselPicture.swift +++ b/Vernissage/Widgets/ImageCarouselPicture.swift @@ -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 } diff --git a/Vernissage/Widgets/ImageRowAsync.swift b/Vernissage/Widgets/ImageRowAsync.swift index ea215b5..1f6aa45 100644 --- a/Vernissage/Widgets/ImageRowAsync.swift +++ b/Vernissage/Widgets/ImageRowAsync.swift @@ -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). diff --git a/Vernissage/Widgets/ImagesCarousel.swift b/Vernissage/Widgets/ImagesCarousel.swift index a41da9c..63b3328 100644 --- a/Vernissage/Widgets/ImagesCarousel.swift +++ b/Vernissage/Widgets/ImagesCarousel.swift @@ -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, exifCamera: Binding, exifExposure: Binding, @@ -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 } diff --git a/Vernissage/Widgets/ImagesViewer.swift b/Vernissage/Widgets/ImagesViewer.swift index b541d3e..a2bc7a6 100644 --- a/Vernissage/Widgets/ImagesViewer.swift +++ b/Vernissage/Widgets/ImagesViewer.swift @@ -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 diff --git a/Vernissage/Widgets/InteractionRow.swift b/Vernissage/Widgets/InteractionRow.swift index ac1b521..f457438 100644 --- a/Vernissage/Widgets/InteractionRow.swift +++ b/Vernissage/Widgets/InteractionRow.swift @@ -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") diff --git a/Vernissage/Widgets/NotificationsView/NotificationRow.swift b/Vernissage/Widgets/NotificationsView/NotificationRow.swift index 6a5faca..d037c5e 100644 --- a/Vernissage/Widgets/NotificationsView/NotificationRow.swift +++ b/Vernissage/Widgets/NotificationsView/NotificationRow.swift @@ -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, diff --git a/Vernissage/Widgets/SettingsView/AccountsSection.swift b/Vernissage/Widgets/SettingsView/AccountsSection.swift index 9bfa4a3..357c4b3 100644 --- a/Vernissage/Widgets/SettingsView/AccountsSection.swift +++ b/Vernissage/Widgets/SettingsView/AccountsSection.swift @@ -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) diff --git a/Vernissage/Widgets/StatusView/CommentBody.swift b/Vernissage/Widgets/StatusView/CommentBody.swift index 579d87a..331320a 100644 --- a/Vernissage/Widgets/StatusView/CommentBody.swift +++ b/Vernissage/Widgets/StatusView/CommentBody.swift @@ -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 } } diff --git a/Vernissage/Widgets/StatusView/CommentsSection.swift b/Vernissage/Widgets/StatusView/CommentsSection.swift index bf55da0..890b34b 100644 --- a/Vernissage/Widgets/StatusView/CommentsSection.swift +++ b/Vernissage/Widgets/StatusView/CommentsSection.swift @@ -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 { diff --git a/Vernissage/Widgets/UserProfile/UserProfileHeader.swift b/Vernissage/Widgets/UserProfile/UserProfileHeader.swift index 3fa961f..6b11f43 100644 --- a/Vernissage/Widgets/UserProfile/UserProfileHeader.swift +++ b/Vernissage/Widgets/UserProfile/UserProfileHeader.swift @@ -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 } diff --git a/Vernissage/Widgets/UserProfile/UserProfileStatuses.swift b/Vernissage/Widgets/UserProfile/UserProfileStatuses.swift index 9712288..1d5b2c6 100644 --- a/Vernissage/Widgets/UserProfile/UserProfileStatuses.swift +++ b/Vernissage/Widgets/UserProfile/UserProfileStatuses.swift @@ -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)