diff --git a/DB/Sources/DB/Content/ContentDatabase.swift b/DB/Sources/DB/Content/ContentDatabase.swift index 361feab..54cf5e6 100644 --- a/DB/Sources/DB/Content/ContentDatabase.swift +++ b/DB/Sources/DB/Content/ContentDatabase.swift @@ -394,11 +394,44 @@ private extension ContentDatabase { try FileManager.default.databaseDirectoryURL(name: id.uuidString) } + // swiftlint:disable:next function_body_length static func clean(_ databaseWriter: DatabaseWriter, useHomeTimelineLastReadId: Bool, useNotificationsLastReadId: Bool) throws { try databaseWriter.write { - try NotificationRecord.deleteAll($0) + let notificationAccountIds: [Account.Id] + let notificationStatusIds: [Status.Id] + + if useNotificationsLastReadId { + var notificationIds = try MastodonNotification.Id.fetchAll( + $0, + NotificationRecord.select(NotificationRecord.Columns.id) + .order(NotificationRecord.Columns.id.desc)) + + if let lastReadId = try MastodonNotification.Id.fetchOne( + $0, + LastReadIdRecord.filter( + LastReadIdRecord.Columns.markerTimeline == Marker.Timeline.notifications.rawValue) + .select(LastReadIdRecord.Columns.id)) + ?? notificationIds.first, + let index = notificationIds.firstIndex(of: lastReadId) { + notificationIds = Array(notificationIds.prefix(index + Self.cleanAfterLastReadIdCount)) + } + + try NotificationRecord.filter(!notificationIds.contains(NotificationRecord.Columns.id)).deleteAll($0) + notificationAccountIds = try Account.Id.fetchAll( + $0, + NotificationRecord.select(NotificationRecord.Columns.accountId)) + notificationStatusIds = try Status.Id.fetchAll( + $0, + NotificationRecord.filter( + NotificationRecord.Columns.statusId != nil) + .select(NotificationRecord.Columns.statusId)) + } else { + try NotificationRecord.deleteAll($0) + notificationAccountIds = [] + notificationStatusIds = [] + } if useHomeTimelineLastReadId { try TimelineRecord.filter(TimelineRecord.Columns.id != Timeline.home.id).deleteAll($0) @@ -416,13 +449,15 @@ private extension ContentDatabase { statusIds = Array(statusIds.prefix(index + Self.cleanAfterLastReadIdCount)) } + statusIds += notificationStatusIds statusIds += try Status.Id.fetchAll( $0, StatusRecord.filter(statusIds.contains(StatusRecord.Columns.id) && StatusRecord.Columns.reblogId != nil) .select(StatusRecord.Columns.reblogId)) - try StatusRecord.filter(!statusIds.contains(StatusRecord.Columns.id) ).deleteAll($0) + try StatusRecord.filter(!statusIds.contains(StatusRecord.Columns.id)).deleteAll($0) var accountIds = try Account.Id.fetchAll($0, StatusRecord.select(StatusRecord.Columns.accountId)) + accountIds += notificationAccountIds accountIds += try Account.Id.fetchAll( $0, AccountRecord.filter(accountIds.contains(AccountRecord.Columns.id) @@ -431,8 +466,8 @@ private extension ContentDatabase { try AccountRecord.filter(!accountIds.contains(AccountRecord.Columns.id)).deleteAll($0) } else { try TimelineRecord.deleteAll($0) - try StatusRecord.deleteAll($0) - try AccountRecord.deleteAll($0) + try StatusRecord.filter(!notificationStatusIds.contains(StatusRecord.Columns.id)).deleteAll($0) + try AccountRecord.filter(!notificationAccountIds.contains(AccountRecord.Columns.id)).deleteAll($0) } try AccountList.deleteAll($0) diff --git a/Mastodon/Sources/Mastodon/Entities/MastodonNotification.swift b/Mastodon/Sources/Mastodon/Entities/MastodonNotification.swift index e968507..e6c3dff 100644 --- a/Mastodon/Sources/Mastodon/Entities/MastodonNotification.swift +++ b/Mastodon/Sources/Mastodon/Entities/MastodonNotification.swift @@ -3,7 +3,7 @@ import Foundation public struct MastodonNotification: Codable, Hashable { - public let id: String + public let id: Id public let type: NotificationType public let account: Account public let status: Status? @@ -16,8 +16,10 @@ public struct MastodonNotification: Codable, Hashable { } } -extension MastodonNotification { - public enum NotificationType: String, Codable, Unknowable { +public extension MastodonNotification { + typealias Id = String + + enum NotificationType: String, Codable, Unknowable { case follow case mention case reblog diff --git a/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift b/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift index 223cb5a..33422b1 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/NotificationsService.swift @@ -37,6 +37,8 @@ public struct NotificationsService { } extension NotificationsService: CollectionService { + public var markerTimeline: Marker.Timeline? { .notifications } + public func request(maxId: String?, minId: String?) -> AnyPublisher { mastodonAPIClient.pagedRequest(NotificationsEndpoint.notifications, maxId: maxId, minId: minId) .handleEvents(receiveOutput: { diff --git a/ViewModels/Sources/ViewModels/CollectionItemsViewModel.swift b/ViewModels/Sources/ViewModels/CollectionItemsViewModel.swift index e332e18..a5146c3 100644 --- a/ViewModels/Sources/ViewModels/CollectionItemsViewModel.swift +++ b/ViewModels/Sources/ViewModels/CollectionItemsViewModel.swift @@ -21,7 +21,7 @@ final public class CollectionItemsViewModel: ObservableObject { private let lastReadId = CurrentValueSubject(nil) private var lastSelectedLoadMore: LoadMore? private var hasRequestedUsingMarker = false - private var hasRememberedPosition = false + private var shouldRestorePositionOfLocalLastReadId = false private var cancellables = Set() public init(collectionService: CollectionService, identification: Identification) { @@ -43,6 +43,8 @@ final public class CollectionItemsViewModel: ObservableObject { .store(in: &cancellables) if let markerTimeline = collectionService.markerTimeline { + shouldRestorePositionOfLocalLastReadId = + identification.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition lastReadId.compactMap { $0 } .removeDuplicates() .debounce(for: 0.5, scheduler: DispatchQueue.global()) @@ -144,13 +146,10 @@ extension CollectionItemsViewModel: CollectionViewModel { public func viewedAtTop(indexPath: IndexPath) { topVisibleIndexPath = indexPath - if items.value.count > indexPath.section, items.value[indexPath.section].count > indexPath.item { - switch items.value[indexPath.section][indexPath.item] { - case let .status(status, _): - lastReadId.send(status.id) - default: - break - } + if !shouldRestorePositionOfLocalLastReadId, + items.value.count > indexPath.section, + items.value[indexPath.section].count > indexPath.item { + lastReadId.send(items.value[indexPath.section][indexPath.item].itemId) } } @@ -279,12 +278,11 @@ private extension CollectionItemsViewModel { let flatItems = items.value.reduce([], +) let flatNewItems = newItems.reduce([], +) - if let markerTimeline = collectionService.markerTimeline, - identification.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition, + if shouldRestorePositionOfLocalLastReadId, + let markerTimeline = collectionService.markerTimeline, let localLastReadId = identification.service.getLocalLastReadId(markerTimeline), - flatItems.contains(where: { $0.itemId == localLastReadId }), - !hasRememberedPosition { - hasRememberedPosition = true + flatNewItems.contains(where: { $0.itemId == localLastReadId }) { + shouldRestorePositionOfLocalLastReadId = false return localLastReadId }