diff --git a/ServiceLayer/Sources/ServiceLayer/Services/CollectionService.swift b/ServiceLayer/Sources/ServiceLayer/Services/CollectionService.swift index 5c5d88b..d965bca 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/CollectionService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/CollectionService.swift @@ -14,6 +14,8 @@ public protocol CollectionService { var navigationService: NavigationService { get } var positionTimeline: Timeline? { get } func request(maxId: String?, minId: String?, search: Search?) -> AnyPublisher + func requestMarkerLastReadId() -> AnyPublisher + func setMarkerLastReadId(_ id: CollectionItem.Id) -> AnyPublisher } extension CollectionService { @@ -30,4 +32,10 @@ extension CollectionService { public var titleLocalizationComponents: AnyPublisher<[String], Never> { Empty().eraseToAnyPublisher() } public var positionTimeline: Timeline? { nil } + + public func requestMarkerLastReadId() -> AnyPublisher { Empty().eraseToAnyPublisher() } + + public func setMarkerLastReadId(_ id: CollectionItem.Id) -> AnyPublisher { + Empty().eraseToAnyPublisher() + } } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index 2c735d2..23168bd 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -119,12 +119,6 @@ public extension IdentityService { .eraseToAnyPublisher() } - func getMarker(_ markerTimeline: Marker.Timeline) -> AnyPublisher { - mastodonAPIClient.request(MarkersEndpoint.get([markerTimeline])) - .compactMap { $0[markerTimeline.rawValue] } - .eraseToAnyPublisher() - } - func getLocalLastReadId(timeline: Timeline) -> String? { contentDatabase.lastReadId(timelineId: timeline.id) } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift b/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift index 1e548d3..8311dee 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift @@ -55,4 +55,16 @@ extension NotificationsService: CollectionService { .flatMap { contentDatabase.insert(notifications: $0.result) } .eraseToAnyPublisher() } + + public func requestMarkerLastReadId() -> AnyPublisher { + mastodonAPIClient.request(MarkersEndpoint.get([.notifications])) + .compactMap { $0.values.first?.lastReadId } + .eraseToAnyPublisher() + } + + public func setMarkerLastReadId(_ id: CollectionItem.Id) -> AnyPublisher { + mastodonAPIClient.request(MarkersEndpoint.post([.notifications: id])) + .compactMap { $0.values.first?.lastReadId } + .eraseToAnyPublisher() + } } diff --git a/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift b/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift index 752cc53..9d95ec2 100644 --- a/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift @@ -21,10 +21,11 @@ public class CollectionItemsViewModel: ObservableObject { private var topVisibleIndexPath = IndexPath(item: 0, section: 0) private let lastReadId = CurrentValueSubject(nil) private var lastSelectedLoadMore: LoadMore? - private var hasRequestedUsingMarker = false - private var markerScrollPositionItemId: CollectionItem.Id? + private var localLastReadId: CollectionItem.Id? + private var markerLastReadId: CollectionItem.Id? private var cancellables = Set() + // swiftlint:disable:next function_body_length public init(collectionService: CollectionService, identityContext: IdentityContext) { self.collectionService = collectionService self.identityContext = identityContext @@ -50,18 +51,32 @@ public class CollectionItemsViewModel: ObservableObject { .sink { _ in } .store(in: &cancellables) + let debouncedLastReadId = lastReadId + .compactMap { $0 } + .removeDuplicates() + .debounce(for: .seconds(Self.lastReadIdDebounceInterval), scheduler: DispatchQueue.global()) + .share() + + debouncedLastReadId + .filter { [weak self] in + guard let markerLastReadId = self?.markerLastReadId else { return false } + + return $0 > markerLastReadId + } + .flatMap { collectionService.setMarkerLastReadId($0) } + .receive(on: DispatchQueue.main) + .sink { _ in } receiveValue: { [weak self] in self?.markerLastReadId = $0 } + .store(in: &cancellables) + if let timeline = collectionService.positionTimeline { if identityContext.appPreferences.positionBehavior(timeline: timeline) == .localRememberPosition { - markerScrollPositionItemId = identityContext.service.getLocalLastReadId(timeline: timeline) + localLastReadId = identityContext.service.getLocalLastReadId(timeline: timeline) } - lastReadId + debouncedLastReadId .filter { _ in identityContext.appPreferences.positionBehavior(timeline: timeline) == .localRememberPosition } - .compactMap { $0 } - .removeDuplicates() - .debounce(for: .seconds(Self.lastReadIdDebounceInterval), scheduler: DispatchQueue.global()) .flatMap { identityContext.service.setLocalLastReadId($0, timeline: timeline) } .sink { _ in } receiveValue: { _ in } .store(in: &cancellables) @@ -119,6 +134,10 @@ extension CollectionItemsViewModel: CollectionViewModel { receiveCompletion: { [weak self] _ in self?.loadingSubject.send(false) }) .sink { _ in } .store(in: &cancellables) + collectionService.requestMarkerLastReadId() + .sink { _ in } receiveValue: { [weak self] in self?.markerLastReadId = $0 } + .store(in: &cancellables) + } public func select(indexPath: IndexPath) { @@ -375,9 +394,9 @@ private extension CollectionItemsViewModel { let items = lastUpdate.sections.map(\.items).reduce([], +) let newItems = newSections.map(\.items).reduce([], +) - if let itemId = markerScrollPositionItemId, + if let itemId = localLastReadId, newItems.contains(where: { $0.itemId == itemId }) { - markerScrollPositionItemId = nil + localLastReadId = nil return itemId }