diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift index 29fe6fab3..0aba23ccf 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift @@ -22,8 +22,8 @@ extension FollowingListViewModel { configuration: UserSection.Configuration( userTableViewCellDelegate: userTableViewCellDelegate ), - followedUsers: followedUserIds.eraseToAnyPublisher(), - blockedUsers: blockedUserIds.eraseToAnyPublisher() + followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), + blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher() ) // workaround to append loader wrong animation issue diff --git a/MastodonIntent/Handler/SendPostIntentHandler.swift b/MastodonIntent/Handler/SendPostIntentHandler.swift index 7f7bdce9b..54fcefe39 100644 --- a/MastodonIntent/Handler/SendPostIntentHandler.swift +++ b/MastodonIntent/Handler/SendPostIntentHandler.swift @@ -69,7 +69,8 @@ extension SendPostIntentHandler: SendPostIntentHandling { domain: authentication.domain, userID: authentication.userID, appAuthorization: .init(accessToken: authentication.appAccessToken), - userAuthorization: .init(accessToken: authentication.userAccessToken) + userAuthorization: .init(accessToken: authentication.userAccessToken), + inMemoryCache: .sharedCache(for: authentication.objectID.description) ) } diff --git a/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift b/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift index ec6cb0bfb..85dc666f2 100644 --- a/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift +++ b/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift @@ -15,19 +15,22 @@ public struct MastodonAuthenticationBox: UserIdentifier { public let userID: MastodonUser.ID public let appAuthorization: Mastodon.API.OAuth.Authorization public let userAuthorization: Mastodon.API.OAuth.Authorization - + public let inMemoryCache: MastodonAccountInMemoryCache + public init( authenticationRecord: ManagedObjectRecord, domain: String, userID: MastodonUser.ID, appAuthorization: Mastodon.API.OAuth.Authorization, - userAuthorization: Mastodon.API.OAuth.Authorization + userAuthorization: Mastodon.API.OAuth.Authorization, + inMemoryCache: MastodonAccountInMemoryCache ) { self.authenticationRecord = authenticationRecord self.domain = domain self.userID = userID self.appAuthorization = appAuthorization self.userAuthorization = userAuthorization + self.inMemoryCache = inMemoryCache } } @@ -39,8 +42,26 @@ extension MastodonAuthenticationBox { domain: authentication.domain, userID: authentication.userID, appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken), - userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken) + userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken), + inMemoryCache: .sharedCache(for: authentication.objectID.description) ) } } + +public class MastodonAccountInMemoryCache { + @Published public var followingUserIds: [String] = [] + @Published public var blockedUserIds: [String] = [] + + static var sharedCaches = [String: MastodonAccountInMemoryCache]() + + public static func sharedCache(for key: String) -> MastodonAccountInMemoryCache { + if let sharedCache = sharedCaches[key] { + return sharedCache + } + + let sharedCache = MastodonAccountInMemoryCache() + sharedCaches[key] = sharedCache + return sharedCache + } +} diff --git a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift index 00b0e46cd..a865286cb 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift @@ -12,6 +12,8 @@ import CoreData import CoreDataStack import MastodonSDK +private typealias IterativeResponse = (ids: [String], maxID: String?) + public final class AuthenticationService: NSObject { var disposeBag = Set() @@ -25,6 +27,46 @@ public final class AuthenticationService: NSObject { // output @Published public var mastodonAuthentications: [ManagedObjectRecord] = [] @Published public var mastodonAuthenticationBoxes: [MastodonAuthenticationBox] = [] + + private func fetchFollowedBlockedUserIds( + _ authBox: MastodonAuthenticationBox, + _ previousFollowingIDs: [String]? = nil, + _ maxID: String? = nil + ) async throws { + guard let apiService = apiService else { return } + + let followingResponse = try await fetchFollowing(maxID, apiService, authBox) + let followingIds = (previousFollowingIDs ?? []) + followingResponse.ids + + if let nextMaxID = followingResponse.maxID { + return try await fetchFollowedBlockedUserIds(authBox, followingIds, nextMaxID) + } + + let blockedIds = try await apiService.getBlocked( + authenticationBox: authBox + ).value.map { $0.id } + + authBox.inMemoryCache.followingUserIds = followingIds + authBox.inMemoryCache.blockedUserIds = blockedIds + } + + private func fetchFollowing( + _ maxID: String?, + _ apiService: APIService, + _ mastodonAuthenticationBox: MastodonAuthenticationBox + ) async throws -> IterativeResponse { + let response = try await apiService.following( + userID: mastodonAuthenticationBox.userID, + maxID: maxID, + authenticationBox: mastodonAuthenticationBox + ) + + let ids: [String] = response.value.map { $0.id } + let maxID: String? = response.link?.maxID + + return (ids, maxID) + } + public let updateActiveUserAccountPublisher = PassthroughSubject() init( @@ -50,6 +92,18 @@ public final class AuthenticationService: NSObject { super.init() mastodonAuthenticationFetchedResultsController.delegate = self + + $mastodonAuthenticationBoxes + .sink { [weak self] boxes in + Task { [weak self] in + for authBox in boxes { + do { try await self?.fetchFollowedBlockedUserIds(authBox) } + catch {} + } + } + } + .store(in: &disposeBag) + // TODO: verify credentials for active authentication diff --git a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift index 65d92fc29..adabd8962 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift @@ -267,7 +267,8 @@ extension NotificationService { domain: authentication.domain, userID: authentication.userID, appAuthorization: .init(accessToken: authentication.appAccessToken), - userAuthorization: .init(accessToken: authentication.userAccessToken) + userAuthorization: .init(accessToken: authentication.userAccessToken), + inMemoryCache: .sharedCache(for: authentication.objectID.description) ) } }