diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index 999eba2dd..8b2aaff2a 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -166,12 +166,9 @@ 50CC4A7F2AA0D3AA0074C845 /* InstanceMetadataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CC4A7E2AA0D3A90074C845 /* InstanceMetadataParser.swift */; }; 50CC4A822AA0D61F0074C845 /* InstanceMetadataParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CC4A812AA0D61F0074C845 /* InstanceMetadataParserTests.swift */; }; 50D61E5B2AA32B9400A926EC /* APISession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D61E5A2AA32B9400A926EC /* APISession.swift */; }; - 50D61E5D2AA4904F00A926EC /* FeedTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D61E5C2AA4904F00A926EC /* FeedTracking.swift */; }; 50DBB8E02A805836002870B1 /* MockErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DBB8DF2A805836002870B1 /* MockErrorHandler.swift */; }; 50EC39B22A346DDC00E014C2 /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EC39B12A346DDC00E014C2 /* URLHandler.swift */; }; 50F2851C2A5C5C1500CF8865 /* TokenRefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F2851B2A5C5C1500CF8865 /* TokenRefreshView.swift */; }; - 50F830F82A4C92BF00D67099 /* FeedTrackerItemProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F830F72A4C92BF00D67099 /* FeedTrackerItemProviding.swift */; }; - 50F830FA2A4C935C00D67099 /* FeedTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F830F92A4C935C00D67099 /* FeedTracker.swift */; }; 6317ABCB2A37292700603D76 /* FeedType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6317ABCA2A37292700603D76 /* FeedType.swift */; }; 6318DE5427FB958800CC2AD6 /* Stickied Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6318DE5327FB958800CC2AD6 /* Stickied Tag.swift */; }; 6318EDC327EE4D7F00BFCAE8 /* Feed Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6318EDC227EE4D7F00BFCAE8 /* Feed Post.swift */; }; @@ -708,12 +705,9 @@ 50CC4A7E2AA0D3A90074C845 /* InstanceMetadataParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceMetadataParser.swift; sourceTree = ""; }; 50CC4A812AA0D61F0074C845 /* InstanceMetadataParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceMetadataParserTests.swift; sourceTree = ""; }; 50D61E5A2AA32B9400A926EC /* APISession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISession.swift; sourceTree = ""; }; - 50D61E5C2AA4904F00A926EC /* FeedTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedTracking.swift; sourceTree = ""; }; 50DBB8DF2A805836002870B1 /* MockErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockErrorHandler.swift; sourceTree = ""; }; 50EC39B12A346DDC00E014C2 /* URLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHandler.swift; sourceTree = ""; }; 50F2851B2A5C5C1500CF8865 /* TokenRefreshView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRefreshView.swift; sourceTree = ""; }; - 50F830F72A4C92BF00D67099 /* FeedTrackerItemProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedTrackerItemProviding.swift; sourceTree = ""; }; - 50F830F92A4C935C00D67099 /* FeedTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedTracker.swift; sourceTree = ""; }; 630D753C27F65E44006E60C9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 6317ABCA2A37292700603D76 /* FeedType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedType.swift; sourceTree = ""; }; 6318DE5327FB958800CC2AD6 /* Stickied Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stickied Tag.swift"; sourceTree = ""; }; @@ -1488,12 +1482,10 @@ path = Mocks; sourceTree = ""; }; - 50F830EC2A4C8F8D00D67099 /* Feed */ = { + 50F830EC2A4C8F8D00D67099 /* Generics */ = { isa = PBXGroup; children = ( - 50F830F72A4C92BF00D67099 /* FeedTrackerItemProviding.swift */, - 50F830F92A4C935C00D67099 /* FeedTracker.swift */, - 50D61E5C2AA4904F00A926EC /* FeedTracking.swift */, + CDB45C5B2AF1A1D800A1FF08 /* CoreTracker.swift */, CD4368AD2AE23ED400BD8BD1 /* StandardTracker.swift */, CD4368AF2AE23F1400BD8BD1 /* ChildTracker.swift */, CD4368B32AE23F3500BD8BD1 /* ChildTrackerProtocol.swift */, @@ -1501,9 +1493,8 @@ CD4368B72AE23F5400BD8BD1 /* ParentTrackerProtocol.swift */, CD4368B92AE23F6400BD8BD1 /* TrackerItem.swift */, CD4368BB2AE23F6F00BD8BD1 /* TrackerSort.swift */, - CDB45C5B2AF1A1D800A1FF08 /* CoreTracker.swift */, ); - path = Feed; + path = Generics; sourceTree = ""; }; 630049EB27EF390900D5105B /* Networking */ = { @@ -1999,7 +1990,7 @@ 6386E0282A03D0B8006B3C1D /* Trackers */ = { isa = PBXGroup; children = ( - 50F830EC2A4C8F8D00D67099 /* Feed */, + 50F830EC2A4C8F8D00D67099 /* Generics */, CDF8425F2A49EA2A00723DA0 /* Inbox */, 6386E02E2A03ED39006B3C1D /* Comment Tracker.swift */, 63344C4E2A07BD2A001BC616 /* Filters Tracker.swift */, @@ -3152,7 +3143,6 @@ CD8C55342A95515C0060B75B /* Onboarding Text.swift in Sources */, CD4368CC2AE242AD00BD8BD1 /* InboxRepository.swift in Sources */, 50C99B602A6299D8005D57DD /* ErrorHandler.swift in Sources */, - 50F830F82A4C92BF00D67099 /* FeedTrackerItemProviding.swift in Sources */, 030D4AE62AA1273200A3393D /* ErrorDetails.swift in Sources */, CD14461B2A5A4B6D00610EF1 /* PostSettingsView.swift in Sources */, CD7B53B52A5F251400006E81 /* CreatePrivateMessageReportRequest.swift in Sources */, @@ -3269,7 +3259,6 @@ 03A1B3F92A8400DD00AB0DE0 /* APIContentViewProtocol.swift in Sources */, 6322A5D027F8629700135D4F /* UserLinkView.swift in Sources */, 030E864C2AC7037F000283A6 /* SearchBarExtensions.swift in Sources */, - 50D61E5D2AA4904F00A926EC /* FeedTracking.swift in Sources */, 6372184B2A3A2AAD008C4816 /* APIPostAggregates.swift in Sources */, 50A8812C2A72D727003E3661 /* CommunityRepository+Dependency.swift in Sources */, 0394398F2A98EB2300463032 /* APIComment+Mock.swift in Sources */, @@ -3327,7 +3316,6 @@ 50A881282A71D66B003E3661 /* APIClient+Community.swift in Sources */, 039C8DB72B35A32D0096BAAF /* AccountSwitcherSettingsView.swift in Sources */, CD29ED472B2E8785006937CE /* EnvironmentValues+NavigationPath.swift in Sources */, - 50F830FA2A4C935C00D67099 /* FeedTracker.swift in Sources */, CD2053142ACBAF150000AA38 /* AvatarType.swift in Sources */, CD69F55D2A400DF50028D4F7 /* UIUserInterfaceStyle+SettingsOptions.swift in Sources */, CDF1EF182A6C40C9003594B6 /* Menu Button.swift in Sources */, diff --git a/Mlem/API/Models/Comments/APICommentReplyView.swift b/Mlem/API/Models/Comments/APICommentReplyView.swift index 25f54455e..1282b89e4 100644 --- a/Mlem/API/Models/Comments/APICommentReplyView.swift +++ b/Mlem/API/Models/Comments/APICommentReplyView.swift @@ -28,10 +28,3 @@ struct APICommentReplyView: Decodable { extension APICommentReplyView: Identifiable { var id: Int { commentReply.id } } - -// MARK: - FeedTrackerItem - -extension APICommentReplyView: FeedTrackerItem { - var uniqueIdentifier: Int { id } - var published: Date { commentReply.published } -} diff --git a/Mlem/API/Models/Messages/APIPrivateMessageView.swift b/Mlem/API/Models/Messages/APIPrivateMessageView.swift index c4ac2f389..55d629b5a 100644 --- a/Mlem/API/Models/Messages/APIPrivateMessageView.swift +++ b/Mlem/API/Models/Messages/APIPrivateMessageView.swift @@ -16,10 +16,3 @@ struct APIPrivateMessageView: Decodable { extension APIPrivateMessageView: Identifiable { var id: Int { privateMessage.id } } - -// MARK: - FeedTrackerItem - -extension APIPrivateMessageView: FeedTrackerItem { - var uniqueIdentifier: Int { id } - var published: Date { privateMessage.published } -} diff --git a/Mlem/API/Models/Person/APIPersonMentionView.swift b/Mlem/API/Models/Person/APIPersonMentionView.swift index 42cc1fbd6..b10151be9 100644 --- a/Mlem/API/Models/Person/APIPersonMentionView.swift +++ b/Mlem/API/Models/Person/APIPersonMentionView.swift @@ -29,10 +29,3 @@ struct APIPersonMentionView: Decodable { extension APIPersonMentionView: Identifiable { var id: Int { personMention.id } } - -// MARK: - FeedTrackerItem - -extension APIPersonMentionView: FeedTrackerItem { - var uniqueIdentifier: Int { id } - var published: Date { personMention.published } -} diff --git a/Mlem/API/Models/Posts/APIPostView.swift b/Mlem/API/Models/Posts/APIPostView.swift index 60ef2106e..38ed51c28 100644 --- a/Mlem/API/Models/Posts/APIPostView.swift +++ b/Mlem/API/Models/Posts/APIPostView.swift @@ -43,10 +43,3 @@ extension APIPostView: Hashable { hasher.combine(post.updated) } } - -// MARK: - FeedTrackerItem - -extension APIPostView: FeedTrackerItem { - var uniqueIdentifier: some Hashable { post.id } - var published: Date { post.published } -} diff --git a/Mlem/API/Requests/Messages/GetPrivateMessages.swift b/Mlem/API/Requests/Messages/GetPrivateMessages.swift index ce9cc975d..10fe0c4d8 100644 --- a/Mlem/API/Requests/Messages/GetPrivateMessages.swift +++ b/Mlem/API/Requests/Messages/GetPrivateMessages.swift @@ -35,9 +35,3 @@ struct GetPrivateMessagesRequest: APIGetRequest { struct GetPrivateMessagesResponse: Decodable { let privateMessages: [APIPrivateMessageView] } - -// MARK: - FeedTrackerItemProviding - -extension GetPrivateMessagesResponse: FeedTrackerItemProviding { - var items: [APIPrivateMessageView] { privateMessages } -} diff --git a/Mlem/API/Requests/Person/GetPersonMentions.swift b/Mlem/API/Requests/Person/GetPersonMentions.swift index 7bf6a2e2f..5ea29084a 100644 --- a/Mlem/API/Requests/Person/GetPersonMentions.swift +++ b/Mlem/API/Requests/Person/GetPersonMentions.swift @@ -38,9 +38,3 @@ struct GetPersonMentionsRequest: APIGetRequest { struct GetPersonMentionsResponse: Decodable { let mentions: [APIPersonMentionView] } - -// MARK: - FeedTrackerItemProviding - -extension GetPersonMentionsResponse: FeedTrackerItemProviding { - var items: [APIPersonMentionView] { mentions } -} diff --git a/Mlem/API/Requests/Person/GetReplies.swift b/Mlem/API/Requests/Person/GetReplies.swift index ab10d459e..edab19476 100644 --- a/Mlem/API/Requests/Person/GetReplies.swift +++ b/Mlem/API/Requests/Person/GetReplies.swift @@ -38,9 +38,3 @@ struct GetRepliesRequest: APIGetRequest { struct GetRepliesResponse: Decodable { let replies: [APICommentReplyView] } - -// MARK: - FeedTrackerItemProviding - -extension GetRepliesResponse: FeedTrackerItemProviding { - var items: [APICommentReplyView] { replies } -} diff --git a/Mlem/API/Requests/Post/GetPosts.swift b/Mlem/API/Requests/Post/GetPosts.swift index 3aa41ab7b..a5d980b0e 100644 --- a/Mlem/API/Requests/Post/GetPosts.swift +++ b/Mlem/API/Requests/Post/GetPosts.swift @@ -61,9 +61,3 @@ struct GetPostsResponse: Decodable { let posts: [APIPostView] let nextPage: String? } - -// MARK: - FeedTrackerItemProviding - -extension GetPostsResponse: FeedTrackerItemProviding { - var items: [APIPostView] { posts } -} diff --git a/Mlem/Models/Content/Post Model.swift b/Mlem/Models/Content/Post Model.swift index d1b532f0f..b5767a3b6 100644 --- a/Mlem/Models/Content/Post Model.swift +++ b/Mlem/Models/Content/Post Model.swift @@ -35,7 +35,7 @@ struct PostModel { self.numReplies = apiPostView.counts.comments self.saved = apiPostView.saved self.read = apiPostView.read - self.published = apiPostView.published + self.published = apiPostView.post.published self.updated = apiPostView.post.updated self.links = PostModel.parseLinks(from: post.body) diff --git a/Mlem/Models/Trackers/Feed/FeedTracker.swift b/Mlem/Models/Trackers/Feed/FeedTracker.swift deleted file mode 100644 index 62eb248bc..000000000 --- a/Mlem/Models/Trackers/Feed/FeedTracker.swift +++ /dev/null @@ -1,185 +0,0 @@ -// -// FeedTracker.swift -// Mlem -// -// Created by mormaer on 28/06/2023. -// -// - -import Dependencies -import Foundation - -class FeedTracker: ObservableObject { - @Dependency(\.apiClient) var apiClient - - // isLoading is accessible but does not publish its state because it triggers lots of unexpected view redraws - private(set) var isLoading: Bool = true - @Published private(set) var items: [Item] - - private(set) var page: Int = 1 - - private var ids: Set = .init(minimumCapacity: 1000) - private let shouldPerformMergeSorting: Bool - let internetSpeed: InternetSpeed - - init( - shouldPerformMergeSorting: Bool = true, - internetSpeed: InternetSpeed, - initialItems: [Item] = .init() - ) { - self.shouldPerformMergeSorting = shouldPerformMergeSorting - self.internetSpeed = internetSpeed - self.items = initialItems - } - - // MARK: - Public methods - - /// A method to determine if the tracker should load more items - /// - Parameter item: The `Item` which is being displayed to the user - /// - Returns: A `Bool` indicating if more items should be loaded - @MainActor func shouldLoadContent(after item: Item) -> Bool { - guard !isLoading else { - return false - } - - let thresholdIndex = items.index(items.endIndex, offsetBy: AppConstants.infiniteLoadThresholdOffset) - if thresholdIndex >= 0, - let itemIndex = items.firstIndex(where: { $0.uniqueIdentifier == item.uniqueIdentifier }), - itemIndex >= thresholdIndex { - return true - } - - return false - } - - /// Similar to above, but only returns true if this item *is* the threshold item - @MainActor func shouldLoadContentPrecisely(after item: Item) -> Bool { - guard !isLoading else { return false } - - let thresholdIndex = max(0, items.index(items.endIndex, offsetBy: AppConstants.infiniteLoadThresholdOffset)) - - if let itemIndex = items.firstIndex(where: { $0.uniqueIdentifier == item.uniqueIdentifier }), - itemIndex == thresholdIndex { - return true - } - - return false - } - - /// A method to perform a request to retrieve tracker items - /// - Parameter request: An `APIRequest` that conforms to `FeedItemProviding` with an `Item` type that matches this trackers generic type - /// - Returns: The `Response` type of the request as a discardable result - @discardableResult func perform( - _ request: Request, - filtering: @escaping (_: Request.Response.Item) -> Bool = { _ in true } - ) async throws -> Request.Response where Request.Response: FeedTrackerItemProviding, Request.Response.Item == Item { - let response = try await retrieveItems(with: request) - - await add(response.items, filtering: filtering) - page += 1 - - return response - } - - /// A method to refresh this tracker, this will reset the state of the tracker to it's first page and set the retrieved items when returned - /// - Parameter request: An `APIRequest` that conforms to `FeedItemProviding` with an `Item` type that matches this trackers generic type - /// - Parameter clearBeforeFetch: If true, causes the tracker to empty its items before it fetches new data - /// - Returns: The `Response` type of the request as a discardable result - @discardableResult func refresh( - _ request: Request, - clearBeforeFetch: Bool = false, - filtering: @escaping (_: Request.Response.Item) -> Bool = { _ in true } - ) async throws -> Request.Response where Request.Response: FeedTrackerItemProviding, Request.Response.Item == Item { - if clearBeforeFetch { - await reset() - } - let response = try await retrieveItems(with: request) - await reset(with: response.items, filteredWith: filtering) - return response - } - - /// A method to add new items into the tracker, duplicate items will be rejected - /// - Parameter newItems: The array of new `Item`'s you wish to add - @MainActor - func add(_ newItems: [Item], filtering: @escaping (_: Item) -> Bool = { _ in true }) { - let accepted = dedupedItems(from: newItems.filter(filtering)) - if !shouldPerformMergeSorting { - RunLoop.main.perform { [self] in - items.append(contentsOf: accepted) - } - return - } - - let merged = merge(arr1: items, arr2: accepted, compare: { $0.published > $1.published }) - RunLoop.main.perform { [self] in - items = merged - } - } - - /// A method to add an item to the start of the current list of items - /// - Parameter newItem: The `Item` you wish to add - @MainActor func prepend(_ newItem: Item) { - guard ids.insert(newItem.uniqueIdentifier).inserted else { - return - } - - items.prepend(newItem) - } - - /// A method to supply an updated item to the tracker - /// - Note: If the `id` of the item is not already in the tracker the `updatedItem` will be discarded - /// - Parameter updatedItem: An updated `Item` - @MainActor func update(with updatedItem: Item) { - guard let index = items.firstIndex(where: { $0.uniqueIdentifier == updatedItem.uniqueIdentifier }) else { - return - } - - items[index] = updatedItem - } - - // MARK: - Private methods - - /// A method to reset the tracker to it's initial state - @MainActor private func reset( - with newItems: [Item] = .init(), - filteredWith filter: @escaping (_: Item) -> Bool = { _ in true } - ) { - page = newItems.isEmpty ? 1 : 2 - ids = .init(minimumCapacity: 1000) - items = dedupedItems(from: newItems.filter(filter)) - } - - @MainActor - private func retrieveItems( - with request: Request - ) async throws -> Request.Response where Request.Response: FeedTrackerItemProviding, Request.Response.Item == Item { - defer { isLoading = false } - isLoading = true - return try await apiClient.perform(request: request) - } - - private func dedupedItems(from newItems: [Item]) -> [Item] { - let accepted = newItems.filter { ids.insert($0.uniqueIdentifier).inserted } - return accepted - } - - // Takes a callback and fillters out any entry that returns false - // - // Returns the number of entries removed - @discardableResult func filter(_ callback: (Item) -> Bool) -> Int { - var removedElements = 0 - - items = items.filter { - let filterResult = callback($0) - - // Remove the ID from the IDs set as well - if !filterResult { - ids.remove($0.uniqueIdentifier) - removedElements += 1 - } - return filterResult - } - - return removedElements - } -} diff --git a/Mlem/Models/Trackers/Feed/FeedTrackerItemProviding.swift b/Mlem/Models/Trackers/Feed/FeedTrackerItemProviding.swift deleted file mode 100644 index ba5f63fa8..000000000 --- a/Mlem/Models/Trackers/Feed/FeedTrackerItemProviding.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// FeedTrackerItemProviding.swift -// Mlem -// -// Created by mormaer on 28/06/2023. -// -// - -import Foundation - -/// A protocol describing an object which can provide `FeedTrackerItem`s -protocol FeedTrackerItemProviding { - associatedtype Item: FeedTrackerItem - var items: [Item] { get } -} - -/// A protocol describing an instance that can provide it's published date -protocol PublishedDateProviding { - var published: Date { get } -} - -/// A protocol describing an item that can be understood by a `FeedTracker` -protocol FeedTrackerItem: Decodable, PublishedDateProviding { - associatedtype UniqueIdentifier: Hashable - var uniqueIdentifier: UniqueIdentifier { get } -} diff --git a/Mlem/Models/Trackers/Feed/FeedTracking.swift b/Mlem/Models/Trackers/Feed/FeedTracking.swift deleted file mode 100644 index 21f145459..000000000 --- a/Mlem/Models/Trackers/Feed/FeedTracking.swift +++ /dev/null @@ -1,160 +0,0 @@ -// -// FeedTracking.swift -// Mlem -// -// Created by mormaer on 03/09/2023. -// -// - -import Foundation - -protocol FeedTracking: ObservableObject { - associatedtype Item: FeedTrackerItem - - var isLoading: Bool { get } - var items: [Item] { get set } - var ids: Set { get set } - var page: Int { get set } - var shouldPerformMergeSorting: Bool { get } - var internetSpeed: InternetSpeed { get } - - func loadNextPage() async throws - func refresh(clearBeforeFetch: Bool) async throws - func retrieveItems(for page: Int) async throws -> [Item] - - func shouldLoadContent(after item: Item) -> Bool - func shouldLoadContentPrecisely(after item: Item) -> Bool - - func add(_ newItems: [Item], filtering: @escaping (Item) -> Bool) - func prepend(_ newItem: Item) - func update(with updatedItem: Item) - - func filter(_ predicate: (Item) -> Bool) -> Int -} - -extension FeedTracking { - @MainActor - func loadNextPage() async throws { - let items = try await retrieveItems(for: page) - add(items, filtering: { _ in true }) - page += 1 - } - - @MainActor - func refresh(clearBeforeFetch: Bool = false) async throws { - if clearBeforeFetch { - reset() - } - - let items = try await retrieveItems(for: 1) - reset(with: items) - } - - /// A method to determine if the tracker should load more items - /// - Parameter item: The `Item` which is being displayed to the user - /// - Returns: A `Bool` indicating if more items should be loaded - @MainActor func shouldLoadContent(after item: Item) -> Bool { - guard !isLoading else { - return false - } - - let thresholdIndex = items.index(items.endIndex, offsetBy: AppConstants.infiniteLoadThresholdOffset) - if thresholdIndex >= 0, - let itemIndex = items.firstIndex(where: { $0.uniqueIdentifier == item.uniqueIdentifier }), - itemIndex >= thresholdIndex { - return true - } - - return false - } - - /// This method is equivalent to `shouldLoadContent(after: ...)` except will only return `true` where the passed in `Item` *is* the threshold item - /// - Parameter item: The `Item` which is being displayed to the user - /// - Returns: A `Bool` indicating if more items should be loaded - @MainActor func shouldLoadContentPrecisely(after item: Item) -> Bool { - guard !isLoading else { return false } - - let thresholdIndex = max(0, items.index(items.endIndex, offsetBy: AppConstants.infiniteLoadThresholdOffset)) - - if let itemIndex = items.firstIndex(where: { $0.uniqueIdentifier == item.uniqueIdentifier }), - itemIndex == thresholdIndex { - return true - } - - return false - } - - /// A method to add new items into the tracker, duplicate items will be rejected - /// - Parameter newItems: The array of new `Item`'s you wish to add - @MainActor - func add(_ newItems: [Item], filtering: @escaping (_: Item) -> Bool = { _ in true }) { - let accepted = dedupedItems(from: newItems.filter(filtering)) - if !shouldPerformMergeSorting { - RunLoop.main.perform { [self] in - items.append(contentsOf: accepted) - } - return - } - - let merged = merge(arr1: items, arr2: accepted, compare: { $0.published > $1.published }) - RunLoop.main.perform { [self] in - items = merged - } - } - - /// A method to add an item to the start of the current list of items - /// - Parameter newItem: The `Item` you wish to add - @MainActor func prepend(_ newItem: Item) { - guard ids.insert(newItem.uniqueIdentifier).inserted else { - return - } - - items.prepend(newItem) - } - - /// A method to supply an updated item to the tracker - /// - Note: If the `id` of the item is not already in the tracker the `updatedItem` will be discarded - /// - Parameter updatedItem: An updated `Item` - @MainActor func update(with updatedItem: Item) { - guard let index = items.firstIndex(where: { $0.uniqueIdentifier == updatedItem.uniqueIdentifier }) else { - return - } - - items[index] = updatedItem - } - - private func dedupedItems(from newItems: [Item]) -> [Item] { - let accepted = newItems.filter { ids.insert($0.uniqueIdentifier).inserted } - return accepted - } - - /// A method to reset the tracker to it's initial state - @MainActor private func reset( - with newItems: [Item] = .init(), - filteredWith filter: @escaping (_: Item) -> Bool = { _ in true } - ) { - page = newItems.isEmpty ? 1 : 2 - ids = .init(minimumCapacity: 1000) - items = dedupedItems(from: newItems.filter(filter)) - } - - /// A method to filter items from the tracker - /// - Parameter predicate: The operation to use when filtering - /// - Returns: The number of items removed by the filter operation - @discardableResult func filter(_ predicate: (Item) -> Bool) -> Int { - var removedElements = 0 - - items = items.filter { - let filterResult = predicate($0) - - // Remove the id from the ids set as well - if !filterResult { - ids.remove($0.uniqueIdentifier) - removedElements += 1 - } - return filterResult - } - - return removedElements - } -} diff --git a/Mlem/Models/Trackers/Feed/ChildTracker.swift b/Mlem/Models/Trackers/Generics/ChildTracker.swift similarity index 100% rename from Mlem/Models/Trackers/Feed/ChildTracker.swift rename to Mlem/Models/Trackers/Generics/ChildTracker.swift diff --git a/Mlem/Models/Trackers/Feed/ChildTrackerProtocol.swift b/Mlem/Models/Trackers/Generics/ChildTrackerProtocol.swift similarity index 100% rename from Mlem/Models/Trackers/Feed/ChildTrackerProtocol.swift rename to Mlem/Models/Trackers/Generics/ChildTrackerProtocol.swift diff --git a/Mlem/Models/Trackers/Feed/CoreTracker.swift b/Mlem/Models/Trackers/Generics/CoreTracker.swift similarity index 100% rename from Mlem/Models/Trackers/Feed/CoreTracker.swift rename to Mlem/Models/Trackers/Generics/CoreTracker.swift diff --git a/Mlem/Models/Trackers/Feed/ParentTracker.swift b/Mlem/Models/Trackers/Generics/ParentTracker.swift similarity index 100% rename from Mlem/Models/Trackers/Feed/ParentTracker.swift rename to Mlem/Models/Trackers/Generics/ParentTracker.swift diff --git a/Mlem/Models/Trackers/Feed/ParentTrackerProtocol.swift b/Mlem/Models/Trackers/Generics/ParentTrackerProtocol.swift similarity index 100% rename from Mlem/Models/Trackers/Feed/ParentTrackerProtocol.swift rename to Mlem/Models/Trackers/Generics/ParentTrackerProtocol.swift diff --git a/Mlem/Models/Trackers/Feed/StandardTracker.swift b/Mlem/Models/Trackers/Generics/StandardTracker.swift similarity index 100% rename from Mlem/Models/Trackers/Feed/StandardTracker.swift rename to Mlem/Models/Trackers/Generics/StandardTracker.swift diff --git a/Mlem/Models/Trackers/Feed/TrackerItem.swift b/Mlem/Models/Trackers/Generics/TrackerItem.swift similarity index 93% rename from Mlem/Models/Trackers/Feed/TrackerItem.swift rename to Mlem/Models/Trackers/Generics/TrackerItem.swift index 907d6abbe..cb251a3ed 100644 --- a/Mlem/Models/Trackers/Feed/TrackerItem.swift +++ b/Mlem/Models/Trackers/Generics/TrackerItem.swift @@ -1,5 +1,5 @@ // -// TrackerSortableNew.swift +// TrackerItem.swift // Mlem // // Created by Eric Andrews on 2023-10-15. diff --git a/Mlem/Models/Trackers/Feed/TrackerSort.swift b/Mlem/Models/Trackers/Generics/TrackerSort.swift similarity index 100% rename from Mlem/Models/Trackers/Feed/TrackerSort.swift rename to Mlem/Models/Trackers/Generics/TrackerSort.swift