diff --git a/ServiceLayer/Sources/ServiceLayer/Services/ContextService.swift b/ServiceLayer/Sources/ServiceLayer/Services/ContextService.swift new file mode 100644 index 0000000..944fd44 --- /dev/null +++ b/ServiceLayer/Sources/ServiceLayer/Services/ContextService.swift @@ -0,0 +1,38 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import DB +import Foundation +import Mastodon +import MastodonAPI + +public struct ContextService: CollectionService { + public let sections: AnyPublisher<[[CollectionItem]], Error> + public let navigationService: NavigationService + public let nextPageMaxIDs: AnyPublisher = Empty().eraseToAnyPublisher() + public let title: String? = nil + public var contextParentID: String? { statusID } + + private let statusID: String + private let mastodonAPIClient: MastodonAPIClient + private let contentDatabase: ContentDatabase + + init(statusID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { + self.statusID = statusID + self.mastodonAPIClient = mastodonAPIClient + self.contentDatabase = contentDatabase + sections = contentDatabase.contextObservation(parentID: statusID) + navigationService = NavigationService( + status: nil, + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase) + } + + public func request(maxID: String?, minID: String?) -> AnyPublisher { + mastodonAPIClient.request(StatusEndpoint.status(id: statusID)) + .flatMap(contentDatabase.insert(status:)) + .merge(with: mastodonAPIClient.request(ContextEndpoint.context(id: statusID)) + .flatMap { contentDatabase.insert(context: $0, parentID: statusID) }) + .eraseToAnyPublisher() + } +} diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index aa8adf7..87f342e 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -177,8 +177,8 @@ public extension IdentityService { .eraseToAnyPublisher() } - func service(timeline: Timeline) -> StatusListService { - StatusListService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) + func service(timeline: Timeline) -> TimelineService { + TimelineService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) } } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/NavigationService.swift b/ServiceLayer/Sources/ServiceLayer/Services/NavigationService.swift index 25a96cd..07bd025 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/NavigationService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/NavigationService.swift @@ -31,7 +31,7 @@ public extension NavigationService { if let tag = tag(url: url) { return Just( .collection( - StatusListService( + TimelineService( timeline: .tag(tag), mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase))) @@ -39,13 +39,7 @@ public extension NavigationService { } else if let accountID = accountID(url: url) { return Just(.profile(profileService(id: accountID))).eraseToAnyPublisher() } else if mastodonAPIClient.instanceURL.host == url.host, let statusID = url.statusID { - return Just( - .collection( - StatusListService( - statusID: statusID, - mastodonAPIClient: mastodonAPIClient, - contentDatabase: contentDatabase))) - .eraseToAnyPublisher() + return Just(.collection(contextService(id: statusID))).eraseToAnyPublisher() } if url.shouldWebfinger { @@ -55,8 +49,8 @@ public extension NavigationService { } } - func contextStatusListService(id: String) -> StatusListService { - StatusListService(statusID: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) + func contextService(id: String) -> ContextService { + ContextService(statusID: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) } func profileService(id: String) -> ProfileService { @@ -113,18 +107,14 @@ private extension NavigationService { .map { results -> Navigation in if let tag = results.hashtags.first { return .collection( - StatusListService( + TimelineService( timeline: .tag(tag.name), mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)) } else if let account = results.accounts.first { return .profile(profileService(account: account)) } else if let status = results.statuses.first { - return .collection( - StatusListService( - statusID: status.id, - mastodonAPIClient: mastodonAPIClient, - contentDatabase: contentDatabase)) + return .collection(contextService(id: status.id)) } else { return .url(url) } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift b/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift index ce5205e..27f89a4 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift @@ -50,8 +50,8 @@ public struct ProfileService { } public extension ProfileService { - func statusListService(profileCollection: ProfileCollection) -> StatusListService { - StatusListService( + func timelineService(profileCollection: ProfileCollection) -> TimelineService { + TimelineService( timeline: .profile(accountId: accountID, profileCollection: profileCollection), mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) diff --git a/ServiceLayer/Sources/ServiceLayer/Services/StatusListService.swift b/ServiceLayer/Sources/ServiceLayer/Services/StatusListService.swift deleted file mode 100644 index 0e01715..0000000 --- a/ServiceLayer/Sources/ServiceLayer/Services/StatusListService.swift +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright © 2020 Metabolist. All rights reserved. - -import Combine -import DB -import Foundation -import Mastodon -import MastodonAPI - -public struct StatusListService: CollectionService { - public let sections: AnyPublisher<[[CollectionItem]], Error> - public let nextPageMaxIDs: AnyPublisher - public let contextParentID: String? - public let title: String? - public let navigationService: NavigationService - - private let filterContext: Filter.Context - private let mastodonAPIClient: MastodonAPIClient - private let contentDatabase: ContentDatabase - private let requestClosure: (_ maxID: String?, _ minID: String?) -> AnyPublisher -} - -extension StatusListService { - init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { - var title: String? - - if case let .tag(tag) = timeline { - title = "#".appending(tag) - } - - let nextPageMaxIDsSubject = PassthroughSubject() - - self.init(sections: contentDatabase.observation(timeline: timeline), - nextPageMaxIDs: nextPageMaxIDsSubject.eraseToAnyPublisher(), - contextParentID: nil, - title: title, - navigationService: NavigationService( - status: nil, - mastodonAPIClient: mastodonAPIClient, - contentDatabase: contentDatabase), - filterContext: timeline.filterContext, - mastodonAPIClient: mastodonAPIClient, - contentDatabase: contentDatabase) { maxID, minID in - mastodonAPIClient.pagedRequest(timeline.endpoint, maxID: maxID, minID: minID) - .handleEvents(receiveOutput: { nextPageMaxIDsSubject.send($0.info.maxID) }) - .flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) } - .eraseToAnyPublisher() - } - } - - init(statusID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { - self.init(sections: contentDatabase.contextObservation(parentID: statusID), - nextPageMaxIDs: Empty().eraseToAnyPublisher(), - contextParentID: statusID, - title: nil, - navigationService: NavigationService( - status: nil, - mastodonAPIClient: mastodonAPIClient, - contentDatabase: contentDatabase), - filterContext: .thread, - mastodonAPIClient: mastodonAPIClient, - contentDatabase: contentDatabase) { _, _ in - Publishers.Merge( - mastodonAPIClient.request(StatusEndpoint.status(id: statusID)) - .flatMap(contentDatabase.insert(status:)) - .eraseToAnyPublisher(), - mastodonAPIClient.request(ContextEndpoint.context(id: statusID)) - .flatMap { contentDatabase.insert(context: $0, parentID: statusID) } - .eraseToAnyPublisher()) - .eraseToAnyPublisher() - } - } -} - -public extension StatusListService { - func request(maxID: String?, minID: String?) -> AnyPublisher { - requestClosure(maxID, minID) - } -} diff --git a/ServiceLayer/Sources/ServiceLayer/Services/TimelineService.swift b/ServiceLayer/Sources/ServiceLayer/Services/TimelineService.swift new file mode 100644 index 0000000..1483e56 --- /dev/null +++ b/ServiceLayer/Sources/ServiceLayer/Services/TimelineService.swift @@ -0,0 +1,45 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import DB +import Foundation +import Mastodon +import MastodonAPI + +public struct TimelineService: CollectionService { + public let sections: AnyPublisher<[[CollectionItem]], Error> + public let navigationService: NavigationService + public let nextPageMaxIDs: AnyPublisher + public let title: String? + public let contextParentID: String? = nil + + private let timeline: Timeline + private let mastodonAPIClient: MastodonAPIClient + private let contentDatabase: ContentDatabase + private let nextPageMaxIDsSubject = PassthroughSubject() + + init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { + self.timeline = timeline + self.mastodonAPIClient = mastodonAPIClient + self.contentDatabase = contentDatabase + sections = contentDatabase.observation(timeline: timeline) + navigationService = NavigationService( + status: nil, + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase) + nextPageMaxIDs = nextPageMaxIDsSubject.eraseToAnyPublisher() + + if case let .tag(tag) = timeline { + title = "#".appending(tag) + } else { + title = nil + } + } + + public func request(maxID: String?, minID: String?) -> AnyPublisher { + mastodonAPIClient.pagedRequest(timeline.endpoint, maxID: maxID, minID: minID) + .handleEvents(receiveOutput: { nextPageMaxIDsSubject.send($0.info.maxID) }) + .flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) } + .eraseToAnyPublisher() + } +} diff --git a/ViewModels/Sources/ViewModels/ListViewModel.swift b/ViewModels/Sources/ViewModels/ListViewModel.swift index 6b144f2..09c9dc9 100644 --- a/ViewModels/Sources/ViewModels/ListViewModel.swift +++ b/ViewModels/Sources/ViewModels/ListViewModel.swift @@ -66,7 +66,7 @@ extension ListViewModel: CollectionViewModel { ListViewModel( collectionService: collectionService .navigationService - .contextStatusListService(id: configuration.status.displayStatus.id)))) + .contextService(id: configuration.status.displayStatus.id)))) case .loadMore: loadMoreViewModel(item: identifier)?.loadMore() case let .account(account): diff --git a/ViewModels/Sources/ViewModels/ProfileViewModel.swift b/ViewModels/Sources/ViewModels/ProfileViewModel.swift index 29ef1a9..ccc61dc 100644 --- a/ViewModels/Sources/ViewModels/ProfileViewModel.swift +++ b/ViewModels/Sources/ViewModels/ProfileViewModel.swift @@ -18,7 +18,7 @@ final public class ProfileViewModel { self.profileService = profileService collectionViewModel = CurrentValueSubject( - ListViewModel(collectionService: profileService.statusListService(profileCollection: .statuses))) + ListViewModel(collectionService: profileService.timelineService(profileCollection: .statuses))) profileService.accountServicePublisher .map(AccountViewModel.init(accountService:)) @@ -26,7 +26,7 @@ final public class ProfileViewModel { .assign(to: &$accountViewModel) $collection.dropFirst() - .map(profileService.statusListService(profileCollection:)) + .map(profileService.timelineService(profileCollection:)) .map(ListViewModel.init(collectionService:)) .sink { [weak self] in guard let self = self else { return }