Refactoring

This commit is contained in:
Justin Mazzocchi 2020-10-06 17:31:29 -07:00
parent dc7d0df55b
commit feef7d794d
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
8 changed files with 68 additions and 83 deletions

View file

@ -8,10 +8,10 @@ final class ProfileViewController: TableViewController {
private let viewModel: ProfileViewModel private let viewModel: ProfileViewModel
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
required init(viewModel: ProfileViewModel) { required init(viewModel: ProfileViewModel, identification: Identification) {
self.viewModel = viewModel self.viewModel = viewModel
super.init(viewModel: viewModel) super.init(viewModel: viewModel, identification: identification)
} }
override func viewDidLoad() { override func viewDidLoad() {

View file

@ -7,6 +7,7 @@ import ViewModels
class TableViewController: UITableViewController { class TableViewController: UITableViewController {
private let viewModel: CollectionViewModel private let viewModel: CollectionViewModel
private let identification: Identification
private let loadingTableFooterView = LoadingTableFooterView() private let loadingTableFooterView = LoadingTableFooterView()
private let webfingerIndicatorView = WebfingerIndicatorView() private let webfingerIndicatorView = WebfingerIndicatorView()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@ -37,8 +38,9 @@ class TableViewController: UITableViewController {
} }
}() }()
init(viewModel: CollectionViewModel) { init(viewModel: CollectionViewModel, identification: Identification) {
self.viewModel = viewModel self.viewModel = viewModel
self.identification = identification
super.init(style: .plain) super.init(style: .plain)
} }
@ -183,25 +185,9 @@ private extension TableViewController {
viewModel.sections.sink { [weak self] in self?.update(items: $0) }.store(in: &cancellables) viewModel.sections.sink { [weak self] in self?.update(items: $0) }.store(in: &cancellables)
viewModel.navigationEvents.receive(on: DispatchQueue.main).sink { [weak self] in viewModel.events.receive(on: DispatchQueue.main)
guard let self = self else { return } .sink { [weak self] in self?.handle(event: $0) }
.store(in: &cancellables)
switch $0 {
case let .share(url):
self.share(url: url)
case let .collectionNavigation(viewModel):
self.show(TableViewController(viewModel: viewModel), sender: self)
case let .profileNavigation(viewModel):
self.show(ProfileViewController(viewModel: viewModel), sender: self)
case let .urlNavigation(url):
self.present(SFSafariViewController(url: url), animated: true)
case .webfingerStart:
self.webfingerIndicatorView.startAnimating()
case .webfingerEnd:
self.webfingerIndicatorView.stopAnimating()
}
}
.store(in: &cancellables)
viewModel.loading.receive(on: RunLoop.main).sink { [weak self] in viewModel.loading.receive(on: RunLoop.main).sink { [weak self] in
guard let self = self else { return } guard let self = self else { return }
@ -246,6 +232,38 @@ private extension TableViewController {
} }
} }
func handle(event: CollectionItemEvent) {
switch event {
case .ignorableOutput:
break
case let .share(url):
share(url: url)
case let.navigation(navigation):
switch navigation {
case let .collection(collectionService):
show(TableViewController(
viewModel: CollectionItemsViewModel(
collectionService: collectionService,
identification: identification),
identification: identification),
sender: self)
case let .profile(profileService):
show(ProfileViewController(
viewModel: ProfileViewModel(
profileService: profileService,
identification: identification),
identification: identification),
sender: self)
case let .url(url):
present(SFSafariViewController(url: url), animated: true)
case .webfingerStart:
webfingerIndicatorView.startAnimating()
case .webfingerEnd:
webfingerIndicatorView.stopAnimating()
}
}
}
func share(url: URL) { func share(url: URL) {
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)

View file

@ -12,15 +12,17 @@ final public class CollectionItemsViewModel: ObservableObject {
private let items = CurrentValueSubject<[[CollectionItem]], Never>([]) private let items = CurrentValueSubject<[[CollectionItem]], Never>([])
private let collectionService: CollectionService private let collectionService: CollectionService
private let identification: Identification
private var viewModelCache = [CollectionItem: (viewModel: CollectionItemViewModel, events: AnyCancellable)]() private var viewModelCache = [CollectionItem: (viewModel: CollectionItemViewModel, events: AnyCancellable)]()
private let navigationEventsSubject = PassthroughSubject<NavigationEvent, Never>() private let eventsSubject = PassthroughSubject<CollectionItemEvent, Never>()
private let loadingSubject = PassthroughSubject<Bool, Never>() private let loadingSubject = PassthroughSubject<Bool, Never>()
private var topVisibleIndexPath = IndexPath(item: 0, section: 0) private var topVisibleIndexPath = IndexPath(item: 0, section: 0)
private var lastSelectedLoadMore: LoadMore? private var lastSelectedLoadMore: LoadMore?
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
init(collectionService: CollectionService) { public init(collectionService: CollectionService, identification: Identification) {
self.collectionService = collectionService self.collectionService = collectionService
self.identification = identification
collectionService.sections collectionService.sections
.handleEvents(receiveOutput: { [weak self] in self?.process(items: $0) }) .handleEvents(receiveOutput: { [weak self] in self?.process(items: $0) })
@ -46,7 +48,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
public var loading: AnyPublisher<Bool, Never> { loadingSubject.eraseToAnyPublisher() } public var loading: AnyPublisher<Bool, Never> { loadingSubject.eraseToAnyPublisher() }
public var navigationEvents: AnyPublisher<NavigationEvent, Never> { navigationEventsSubject.eraseToAnyPublisher() } public var events: AnyPublisher<CollectionItemEvent, Never> { eventsSubject.eraseToAnyPublisher() }
public func request(maxId: String? = nil, minId: String? = nil) { public func request(maxId: String? = nil, minId: String? = nil) {
collectionService.request(maxId: maxId, minId: minId) collectionService.request(maxId: maxId, minId: minId)
@ -64,20 +66,18 @@ extension CollectionItemsViewModel: CollectionViewModel {
switch item { switch item {
case let .status(status, _): case let .status(status, _):
navigationEventsSubject.send( eventsSubject.send(
.collectionNavigation( .navigation(.collection(collectionService
CollectionItemsViewModel( .navigationService
collectionService: collectionService .contextService(id: status.displayStatus.id))))
.navigationService
.contextService(id: status.displayStatus.id))))
case let .loadMore(loadMore): case let .loadMore(loadMore):
lastSelectedLoadMore = loadMore lastSelectedLoadMore = loadMore
(viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loadMore() (viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loadMore()
case let .account(account): case let .account(account):
navigationEventsSubject.send( eventsSubject.send(
.profileNavigation( .navigation(.profile(collectionService
ProfileViewModel( .navigationService
profileService: collectionService.navigationService.profileService(account: account)))) .profileService(account: account))))
} }
} }
@ -142,9 +142,9 @@ extension CollectionItemsViewModel: CollectionViewModel {
private extension CollectionItemsViewModel { private extension CollectionItemsViewModel {
func cache(viewModel: CollectionItemViewModel, forItem item: CollectionItem) { func cache(viewModel: CollectionItemViewModel, forItem item: CollectionItem) {
viewModelCache[item] = (viewModel, viewModel.events.flatMap { $0.compactMap(NavigationEvent.init) } viewModelCache[item] = (viewModel, viewModel.events.flatMap { $0 }
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)
.sink { [weak self] in self?.navigationEventsSubject.send($0) }) .sink { [weak self] in self?.eventsSubject.send($0) })
} }
func process(items: [[CollectionItem]]) { func process(items: [[CollectionItem]]) {

View file

@ -8,7 +8,7 @@ public protocol CollectionViewModel {
var title: AnyPublisher<String, Never> { get } var title: AnyPublisher<String, Never> { get }
var alertItems: AnyPublisher<AlertItem, Never> { get } var alertItems: AnyPublisher<AlertItem, Never> { get }
var loading: AnyPublisher<Bool, Never> { get } var loading: AnyPublisher<Bool, Never> { get }
var navigationEvents: AnyPublisher<NavigationEvent, Never> { get } var events: AnyPublisher<CollectionItemEvent, Never> { get }
var nextPageMaxId: String? { get } var nextPageMaxId: String? { get }
var maintainScrollPositionOfItem: CollectionItemIdentifier? { get } var maintainScrollPositionOfItem: CollectionItemIdentifier? { get }
func request(maxId: String?, minId: String?) func request(maxId: String?, minId: String?)

View file

@ -1,36 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public enum NavigationEvent {
case collectionNavigation(CollectionViewModel)
case profileNavigation(ProfileViewModel)
case urlNavigation(URL)
case share(URL)
case webfingerStart
case webfingerEnd
}
extension NavigationEvent {
public init?(_ event: CollectionItemEvent) {
switch event {
case .ignorableOutput:
return nil
case let .navigation(item):
switch item {
case let .url(url):
self = .urlNavigation(url)
case let .collection(statusListService):
self = .collectionNavigation(CollectionItemsViewModel(collectionService: statusListService))
case let .profile(profileService):
self = .profileNavigation(ProfileViewModel(profileService: profileService))
case .webfingerStart:
self = .webfingerStart
case .webfingerEnd:
self = .webfingerEnd
}
case let .share(url):
self = .share(url)
}
}
}

View file

@ -91,7 +91,9 @@ public extension NavigationViewModel {
} }
func viewModel(timeline: Timeline) -> CollectionItemsViewModel { func viewModel(timeline: Timeline) -> CollectionItemsViewModel {
CollectionItemsViewModel(collectionService: identification.service.service(timeline: timeline)) CollectionItemsViewModel(
collectionService: identification.service.service(timeline: timeline),
identification: identification)
} }
} }

View file

@ -14,11 +14,13 @@ final public class ProfileViewModel {
private let collectionViewModel: CurrentValueSubject<CollectionItemsViewModel, Never> private let collectionViewModel: CurrentValueSubject<CollectionItemsViewModel, Never>
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
init(profileService: ProfileService) { public init(profileService: ProfileService, identification: Identification) {
self.profileService = profileService self.profileService = profileService
collectionViewModel = CurrentValueSubject( collectionViewModel = CurrentValueSubject(
CollectionItemsViewModel(collectionService: profileService.timelineService(profileCollection: .statuses))) CollectionItemsViewModel(
collectionService: profileService.timelineService(profileCollection: .statuses),
identification: identification))
profileService.accountServicePublisher profileService.accountServicePublisher
.map(AccountViewModel.init(accountService:)) .map(AccountViewModel.init(accountService:))
@ -27,7 +29,7 @@ final public class ProfileViewModel {
$collection.dropFirst() $collection.dropFirst()
.map(profileService.timelineService(profileCollection:)) .map(profileService.timelineService(profileCollection:))
.map(CollectionItemsViewModel.init(collectionService:)) .map { CollectionItemsViewModel(collectionService: $0, identification: identification) }
.sink { [weak self] in .sink { [weak self] in
guard let self = self else { return } guard let self = self else { return }
@ -55,14 +57,12 @@ extension ProfileViewModel: CollectionViewModel {
collectionViewModel.flatMap(\.loading).eraseToAnyPublisher() collectionViewModel.flatMap(\.loading).eraseToAnyPublisher()
} }
public var navigationEvents: AnyPublisher<NavigationEvent, Never> { public var events: AnyPublisher<CollectionItemEvent, Never> {
$accountViewModel.compactMap { $0 } $accountViewModel.compactMap { $0 }
.flatMap(\.events) .flatMap(\.events)
.flatMap { $0 } .flatMap { $0 }
.map(NavigationEvent.init)
.compactMap { $0 }
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)
.merge(with: collectionViewModel.flatMap(\.navigationEvents)) .merge(with: collectionViewModel.flatMap(\.events))
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }

View file

@ -4,10 +4,11 @@ import SwiftUI
import ViewModels import ViewModels
struct TableView: UIViewControllerRepresentable { struct TableView: UIViewControllerRepresentable {
@EnvironmentObject var identification: Identification
let viewModel: CollectionViewModel let viewModel: CollectionViewModel
func makeUIViewController(context: Context) -> TableViewController { func makeUIViewController(context: Context) -> TableViewController {
TableViewController(viewModel: viewModel) TableViewController(viewModel: viewModel, identification: identification)
} }
func updateUIViewController(_ uiViewController: TableViewController, context: Context) { func updateUIViewController(_ uiViewController: TableViewController, context: Context) {