From a2f84197ef290c3ed65baf46617f761d5d7c5b00 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Thu, 24 Sep 2020 22:39:06 -0700 Subject: [PATCH] Account list wip --- .../Endpoints/AccountsEndpoint.swift | 34 +++++ .../Services/AccountListService.swift | 27 +++- .../Services/AccountService.swift | 4 +- ...LService.swift => NavigationService.swift} | 49 +++++-- .../Services/StatusListService.swift | 70 +++++----- .../ServiceLayer/Services/StatusService.swift | 11 +- .../ViewModels/AccountListViewModel.swift | 123 +++++++++++++++++- .../Sources/ViewModels/AccountViewModel.swift | 4 + .../Entities/CollectionItemEvent.swift | 11 ++ .../ViewModels/Entities/NavigationEvent.swift | 22 ++++ .../ViewModels/StatusListViewModel.swift | 65 +++------ .../Sources/ViewModels/StatusViewModel.swift | 36 ++--- 12 files changed, 343 insertions(+), 113 deletions(-) create mode 100644 MastodonAPI/Sources/MastodonAPI/Endpoints/AccountsEndpoint.swift rename ServiceLayer/Sources/ServiceLayer/Services/{URLService.swift => NavigationService.swift} (59%) create mode 100644 ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/AccountsEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/AccountsEndpoint.swift new file mode 100644 index 0000000..404d646 --- /dev/null +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/AccountsEndpoint.swift @@ -0,0 +1,34 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation +import HTTP +import Mastodon + +public enum AccountsEndpoint { + case statusFavouritedBy(id: String) +} + +extension AccountsEndpoint: Endpoint { + public typealias ResultType = [Account] + + public var context: [String] { + switch self { + case .statusFavouritedBy: + return defaultContext + ["statuses"] + } + } + + public var pathComponentsInContext: [String] { + switch self { + case let .statusFavouritedBy(id): + return [id, "favourited_by"] + } + } + + public var method: HTTPMethod { + switch self { + case .statusFavouritedBy: + return .get + } + } +} diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AccountListService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AccountListService.swift index ddff731..b65fdb5 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AccountListService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AccountListService.swift @@ -8,7 +8,8 @@ import MastodonAPI public struct AccountListService { public let accountSections: AnyPublisher<[[Account]], Error> - public let paginates: Bool + public let nextPageMaxIDs: AnyPublisher + public let navigationService: NavigationService private let mastodonAPIClient: MastodonAPIClient private let contentDatabase: ContentDatabase @@ -16,7 +17,31 @@ public struct AccountListService { } extension AccountListService { + init(favoritedByStatusID statusID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { + let accountSectionsSubject = PassthroughSubject<[[Account]], Error>() + let nextPageMaxIDsSubject = PassthroughSubject() + self.init( + accountSections: accountSectionsSubject.eraseToAnyPublisher(), + nextPageMaxIDs: nextPageMaxIDsSubject.eraseToAnyPublisher(), + navigationService: NavigationService( + status: nil, + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase), + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase) { maxID, minID -> AnyPublisher in + mastodonAPIClient.pagedRequest( + AccountsEndpoint.statusFavouritedBy(id: statusID), maxID: maxID, minID: minID) + .handleEvents( + receiveOutput: { + nextPageMaxIDsSubject.send($0.info.maxID) + accountSectionsSubject.send([$0.result]) + }, + receiveCompletion: accountSectionsSubject.send) + .flatMap { contentDatabase.insert(accounts: $0.result) } + .eraseToAnyPublisher() + } + } } public extension AccountListService { diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift index 12c826f..a639105 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift @@ -8,13 +8,13 @@ import MastodonAPI public struct AccountService { public let account: Account - public let urlService: URLService + public let navigationService: NavigationService private let mastodonAPIClient: MastodonAPIClient private let contentDatabase: ContentDatabase init(account: Account, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { self.account = account - self.urlService = URLService( + self.navigationService = NavigationService( status: nil, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) diff --git a/ServiceLayer/Sources/ServiceLayer/Services/URLService.swift b/ServiceLayer/Sources/ServiceLayer/Services/NavigationService.swift similarity index 59% rename from ServiceLayer/Sources/ServiceLayer/Services/URLService.swift rename to ServiceLayer/Sources/ServiceLayer/Services/NavigationService.swift index 3787191..2c7da81 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/URLService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/NavigationService.swift @@ -6,14 +6,13 @@ import Foundation import Mastodon import MastodonAPI -public enum URLItem { +public enum Navigation { case url(URL) - case statusID(String) - case accountID(String) - case tag(String) + case statusList(StatusListService) + case accountStatuses(AccountStatusesService) } -public struct URLService { +public struct NavigationService { private let status: Status? private let mastodonAPIClient: MastodonAPIClient private let contentDatabase: ContentDatabase @@ -25,21 +24,49 @@ public struct URLService { } } -public extension URLService { - func item(url: URL) -> AnyPublisher { +public extension NavigationService { + func item(url: URL) -> AnyPublisher { if let tag = tag(url: url) { - return Just(.tag(tag)).eraseToAnyPublisher() + return Just( + .statusList( + StatusListService( + timeline: .tag(tag), + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase))) + .eraseToAnyPublisher() } else if let accountID = accountID(url: url) { - return Just(.accountID(accountID)).eraseToAnyPublisher() + return Just(.accountStatuses(accountStatusesService(id: accountID))).eraseToAnyPublisher() } else if mastodonAPIClient.instanceURL.host == url.host, let statusID = url.statusID { - return Just(.statusID(statusID)).eraseToAnyPublisher() + return Just( + .statusList( + StatusListService( + statusID: statusID, + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase))) + .eraseToAnyPublisher() } return Just(.url(url)).eraseToAnyPublisher() } + + func contextStatusListService(id: String) -> StatusListService { + StatusListService(statusID: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) + } + + func accountStatusesService(id: String) -> AccountStatusesService { + AccountStatusesService(id: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) + } + + func statusService(status: Status) -> StatusService { + StatusService(status: status, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) + } + + func accountService(account: Account) -> AccountService { + AccountService(account: account, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) + } } -private extension URLService { +private extension NavigationService { func tag(url: URL) -> String? { if status?.tags.first(where: { $0.url.path.lowercased() == url.path.lowercased() }) != nil { return url.lastPathComponent diff --git a/ServiceLayer/Sources/ServiceLayer/Services/StatusListService.swift b/ServiceLayer/Sources/ServiceLayer/Services/StatusListService.swift index 07a1c0b..380c65c 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/StatusListService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/StatusListService.swift @@ -11,6 +11,7 @@ public struct StatusListService { 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 @@ -41,6 +42,10 @@ extension StatusListService { nextPageMaxIDs: nextPageMaxIDsSubject.eraseToAnyPublisher(), contextParentID: nil, title: title, + navigationService: NavigationService( + status: nil, + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase), filterContext: filterContext, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) { maxID, minID in @@ -51,6 +56,29 @@ extension StatusListService { } } + init(statusID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { + self.init(statusSections: 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() + } + } + init( accountID: String, collection: CurrentValueSubject, @@ -65,6 +93,10 @@ extension StatusListService { nextPageMaxIDs: nextPageMaxIDsSubject.eraseToAnyPublisher(), contextParentID: nil, title: nil, + navigationService: NavigationService( + status: nil, + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase), filterContext: .account, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) { maxID, minID in @@ -90,7 +122,12 @@ extension StatusListService { pinned: false) return mastodonAPIClient.pagedRequest(endpoint, maxID: maxID, minID: minID) .handleEvents(receiveOutput: { nextPageMaxIDsSubject.send($0.info.maxID) }) - .flatMap { contentDatabase.insert(statuses: $0.result, accountID: accountID, collection: collection.value) } + .flatMap { + contentDatabase.insert( + statuses: $0.result, + accountID: accountID, + collection: collection.value) + } .eraseToAnyPublisher() } } @@ -104,35 +141,4 @@ public extension StatusListService { var filters: AnyPublisher<[Filter], Error> { contentDatabase.activeFiltersObservation(date: Date(), context: filterContext) } - - func statusService(status: Status) -> StatusService { - StatusService(status: status, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) - } - - func service(timeline: Timeline) -> Self { - Self(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) - } - - func service(accountID: String) -> AccountStatusesService { - AccountStatusesService(id: accountID, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) - } - - func contextService(statusID: String) -> Self { - Self(statusSections: contentDatabase.contextObservation(parentID: statusID), - nextPageMaxIDs: Empty().eraseToAnyPublisher(), - contextParentID: statusID, - title: nil, - 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() - } - } } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift index 480960c..58fac09 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/StatusService.swift @@ -8,13 +8,13 @@ import MastodonAPI public struct StatusService { public let status: Status - public let urlService: URLService + public let navigationService: NavigationService private let mastodonAPIClient: MastodonAPIClient private let contentDatabase: ContentDatabase init(status: Status, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { self.status = status - self.urlService = URLService( + self.navigationService = NavigationService( status: status.displayStatus, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) @@ -31,4 +31,11 @@ public extension StatusService { .flatMap(contentDatabase.insert(status:)) .eraseToAnyPublisher() } + + func favoritedByService() -> AccountListService { + AccountListService( + favoritedByStatusID: status.id, + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase) + } } diff --git a/ViewModels/Sources/ViewModels/AccountListViewModel.swift b/ViewModels/Sources/ViewModels/AccountListViewModel.swift index 0da31a7..d851cbe 100644 --- a/ViewModels/Sources/ViewModels/AccountListViewModel.swift +++ b/ViewModels/Sources/ViewModels/AccountListViewModel.swift @@ -1,7 +1,126 @@ // Copyright © 2020 Metabolist. All rights reserved. +import Combine import Foundation +import Mastodon +import ServiceLayer -public class AccountListViewModel: ObservableObject { - +public final class AccountListViewModel: ObservableObject { + @Published public private(set) var items = [[CollectionItem]]() + @Published public var alertItem: AlertItem? + public let navigationEvents: AnyPublisher + public private(set) var nextPageMaxID: String? + + private let accountListService: AccountListService + private var accounts = [String: Account]() + private var accountViewModelCache = [Account: (AccountViewModel, AnyCancellable)]() + private let navigationEventsSubject = PassthroughSubject() + private let loadingSubject = PassthroughSubject() + private var cancellables = Set() + + init(accountListService: AccountListService) { + self.accountListService = accountListService + navigationEvents = navigationEventsSubject.eraseToAnyPublisher() + + accountListService.accountSections + .handleEvents(receiveOutput: { [weak self] in + self?.cleanViewModelCache(newAccountSections: $0) + self?.accounts = Dictionary(uniqueKeysWithValues: Set($0.reduce([], +)).map { ($0.id, $0) }) + }) + .map { $0.map { $0.map { CollectionItem(id: $0.id, kind: .account) } } } + .receive(on: DispatchQueue.main) + .assignErrorsToAlertItem(to: \.alertItem, on: self) + .assign(to: &$items) + + accountListService.nextPageMaxIDs + .sink { [weak self] in self?.nextPageMaxID = $0 } + .store(in: &cancellables) + } +} + +extension AccountListViewModel: CollectionViewModel { + public var collectionItems: AnyPublisher<[[CollectionItem]], Never> { $items.eraseToAnyPublisher() } + + public var title: AnyPublisher { Just(nil).eraseToAnyPublisher() } + + public var alertItems: AnyPublisher { $alertItem.compactMap { $0 }.eraseToAnyPublisher() } + + public var loading: AnyPublisher { loadingSubject.eraseToAnyPublisher() } + + public var maintainScrollPositionOfItem: CollectionItem? { + nil + } + + public func request(maxID: String?, minID: String?) { + accountListService.request(maxID: maxID, minID: minID) + .receive(on: DispatchQueue.main) + .assignErrorsToAlertItem(to: \.alertItem, on: self) + .handleEvents( + receiveSubscription: { [weak self] _ in self?.loadingSubject.send(true) }, + receiveCompletion: { [weak self] _ in self?.loadingSubject.send(false) }) + .sink { _ in } + .store(in: &cancellables) + } + + public func itemSelected(_ item: CollectionItem) { + switch item.kind { + case .account: + navigationEventsSubject.send( + .collectionNavigation( + AccountStatusesViewModel( + accountStatusesService: accountListService + .navigationService + .accountStatusesService(id: item.id)))) + default: + break + } + } + + public func canSelect(item: CollectionItem) -> Bool { + true + } + + public func viewModel(item: CollectionItem) -> Any? { + switch item.kind { + case .account: + return accountViewModel(id: item.id) + default: + return nil + } + } +} + +private extension AccountListViewModel { + func accountViewModel(id: String) -> AccountViewModel? { + guard let account = accounts[id] else { return nil } + + var accountViewModel: AccountViewModel + + if let cachedViewModel = accountViewModelCache[account]?.0 { + accountViewModel = cachedViewModel + } else { + accountViewModel = AccountViewModel( + accountService: accountListService.navigationService.accountService(account: account)) + accountViewModelCache[account] = (accountViewModel, + accountViewModel.events + .flatMap { $0 } + .assignErrorsToAlertItem(to: \.alertItem, on: self) + .sink { [weak self] in + guard + let self = self, + let event = NavigationEvent($0) + else { return } + + self.navigationEventsSubject.send(event) + }) + } + + return accountViewModel + } + + func cleanViewModelCache(newAccountSections: [[Account]]) { + let newAccounts = Set(newAccountSections.reduce([], +)) + + accountViewModelCache = accountViewModelCache.filter { newAccounts.contains($0.key) } + } } diff --git a/ViewModels/Sources/ViewModels/AccountViewModel.swift b/ViewModels/Sources/ViewModels/AccountViewModel.swift index 2f8974c..de0edac 100644 --- a/ViewModels/Sources/ViewModels/AccountViewModel.swift +++ b/ViewModels/Sources/ViewModels/AccountViewModel.swift @@ -6,10 +6,14 @@ import Mastodon import ServiceLayer public class AccountViewModel: ObservableObject { + public let events: AnyPublisher, Never> + private let accountService: AccountService + private let eventsSubject = PassthroughSubject, Never>() init(accountService: AccountService) { self.accountService = accountService + events = eventsSubject.eraseToAnyPublisher() } } diff --git a/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift new file mode 100644 index 0000000..da269b2 --- /dev/null +++ b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift @@ -0,0 +1,11 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation +import ServiceLayer + +public enum CollectionItemEvent { + case ignorableOutput + case navigation(Navigation) + case accountListNavigation(AccountListViewModel) + case share(URL) +} diff --git a/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift b/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift index 0a84c2a..f9ae679 100644 --- a/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift +++ b/ViewModels/Sources/ViewModels/Entities/NavigationEvent.swift @@ -7,3 +7,25 @@ public enum NavigationEvent { case urlNavigation(URL) case share(URL) } + +extension NavigationEvent { + init?(_ event: CollectionItemEvent) { + switch event { + case .ignorableOutput: + return nil + case let .navigation(item): + switch item { + case let .url(url): + self = .urlNavigation(url) + case let .statusList(statusListService): + self = .collectionNavigation(StatusListViewModel(statusListService: statusListService)) + case let .accountStatuses(accountStatusesService): + self = .collectionNavigation(AccountStatusesViewModel(accountStatusesService: accountStatusesService)) + } + case let .accountListNavigation(accountListViewModel): + self = .collectionNavigation(accountListViewModel) + case let .share(url): + self = .share(url) + } + } +} diff --git a/ViewModels/Sources/ViewModels/StatusListViewModel.swift b/ViewModels/Sources/ViewModels/StatusListViewModel.swift index f76f67b..0dba126 100644 --- a/ViewModels/Sources/ViewModels/StatusListViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusListViewModel.swift @@ -64,9 +64,7 @@ extension StatusListViewModel: CollectionViewModel { public var alertItems: AnyPublisher { $alertItem.compactMap { $0 }.eraseToAnyPublisher() } - public var loading: AnyPublisher { - loadingSubject.eraseToAnyPublisher() - } + public var loading: AnyPublisher { loadingSubject.eraseToAnyPublisher() } public func itemSelected(_ item: CollectionItem) { switch item.kind { @@ -76,7 +74,9 @@ extension StatusListViewModel: CollectionViewModel { navigationEventsSubject.send( .collectionNavigation( StatusListViewModel( - statusListService: statusListService.contextService(statusID: displayStatusID)))) + statusListService: statusListService + .navigationService + .contextStatusListService(id: displayStatusID)))) default: break } @@ -100,7 +100,15 @@ extension StatusListViewModel: CollectionViewModel { } } -public extension StatusListViewModel { +private extension StatusListViewModel { + static func filter(statusSections: [[Status]], regularExpression: String?) -> [[Status]] { + guard let regEx = regularExpression else { return statusSections } + + return statusSections.map { + $0.filter { $0.filterableContent.range(of: regEx, options: [.regularExpression, .caseInsensitive]) == nil } + } + } + var contextParentID: String? { statusListService.contextParentID } func statusViewModel(id: String) -> StatusViewModel? { @@ -111,15 +119,18 @@ public extension StatusListViewModel { if let cachedViewModel = statusViewModelCache[status]?.0 { statusViewModel = cachedViewModel } else { - statusViewModel = StatusViewModel(statusService: statusListService.statusService(status: status)) + statusViewModel = StatusViewModel( + statusService: statusListService.navigationService.statusService(status: status)) statusViewModelCache[status] = (statusViewModel, statusViewModel.events .flatMap { $0 } .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { [weak self] in - guard let self = self, - let event = self.navigationEvent(statusEvent: $0) + guard + let self = self, + let event = NavigationEvent($0) else { return } + self.navigationEventsSubject.send(event) }) } @@ -131,44 +142,6 @@ public extension StatusListViewModel { return statusViewModel } -} - -private extension StatusListViewModel { - static func filter(statusSections: [[Status]], regularExpression: String?) -> [[Status]] { - guard let regEx = regularExpression else { return statusSections } - - return statusSections.map { - $0.filter { $0.filterableContent.range(of: regEx, options: [.regularExpression, .caseInsensitive]) == nil } - } - } - - func navigationEvent(statusEvent: StatusViewModel.Event) -> NavigationEvent? { - switch statusEvent { - case .ignorableOutput: - return nil - case let .navigation(item): - switch item { - case let .url(url): - return .urlNavigation(url) - case let .accountID(id): - return .collectionNavigation( - AccountStatusesViewModel(accountStatusesService: statusListService.service(accountID: id))) - case let .statusID(id): - return .collectionNavigation( - StatusListViewModel( - statusListService: statusListService.contextService(statusID: id))) - case let .tag(tag): - return .collectionNavigation( - StatusListViewModel( - statusListService: statusListService.service(timeline: Timeline.tag(tag)))) - } - case let .accountListNavigation(accountListViewModel): -// return .collectionNavigation(accountListViewModel) - return nil - case let .share(url): - return .share(url) - } - } func determineIfScrollPositionShouldBeMaintained(newStatusSections: [[Status]]) { maintainScrollPositionOfItem = nil // clear old value diff --git a/ViewModels/Sources/ViewModels/StatusViewModel.swift b/ViewModels/Sources/ViewModels/StatusViewModel.swift index 9593975..196289c 100644 --- a/ViewModels/Sources/ViewModels/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusViewModel.swift @@ -22,10 +22,10 @@ public struct StatusViewModel { public var isReplyInContext = false public var hasReplyFollowing = false public var sensitiveContentToggled = false - public let events: AnyPublisher, Never> + public let events: AnyPublisher, Never> private let statusService: StatusService - private let eventsSubject = PassthroughSubject, Never>() + private let eventsSubject = PassthroughSubject, Never>() init(statusService: StatusService) { self.statusService = statusService @@ -49,15 +49,6 @@ public struct StatusViewModel { } } -public extension StatusViewModel { - enum Event { - case ignorableOutput - case navigation(URLItem) - case accountListNavigation(AccountListViewModel) - case share(URL) - } -} - public extension StatusViewModel { var shouldDisplaySensitiveContent: Bool { if statusService.status.displayStatus.sensitive { @@ -118,31 +109,42 @@ public extension StatusViewModel { func urlSelected(_ url: URL) { eventsSubject.send( - statusService.urlService.item(url: url) - .map { Event.navigation($0) } + statusService.navigationService.item(url: url) + .map { CollectionItemEvent.navigation($0) } .setFailureType(to: Error.self) .eraseToAnyPublisher()) } func accountSelected() { eventsSubject.send( - Just(Event.navigation(.accountID(statusService.status.displayStatus.account.id))) + Just(CollectionItemEvent.navigation( + .accountStatuses( + statusService.navigationService.accountStatusesService( + id: statusService.status.displayStatus.account.id)))) .setFailureType(to: Error.self) .eraseToAnyPublisher()) } func favoritedBySelected() { - + eventsSubject.send( + Just(CollectionItemEvent.accountListNavigation( + AccountListViewModel( + accountListService: statusService.favoritedByService()))) + .setFailureType(to: Error.self) + .eraseToAnyPublisher()) } func toggleFavorited() { - eventsSubject.send(statusService.toggleFavorited().map { _ in Event.ignorableOutput }.eraseToAnyPublisher()) + eventsSubject.send( + statusService.toggleFavorited() + .map { _ in CollectionItemEvent.ignorableOutput } + .eraseToAnyPublisher()) } func shareStatus() { guard let url = statusService.status.displayStatus.url else { return } - eventsSubject.send(Just(Event.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher()) + eventsSubject.send(Just(CollectionItemEvent.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher()) } }