diff --git a/View Controllers/StatusListViewController.swift b/View Controllers/StatusListViewController.swift index 33178ae..056a005 100644 --- a/View Controllers/StatusListViewController.swift +++ b/View Controllers/StatusListViewController.swift @@ -78,6 +78,15 @@ final class StatusListViewController: UITableViewController { } .store(in: &cancellables) + viewModel.statusEvents.sink { [weak self] in + switch $0 { + case .ignorableOutput, .statusListNavigation, .urlNavigation: break + case let .share(url): + self?.share(url: url) + } + } + .store(in: &cancellables) + viewModel.$loading .receive(on: RunLoop.main) .sink { [weak self] in @@ -146,6 +155,12 @@ extension StatusListViewController: UITableViewDataSourcePrefetching { } private extension StatusListViewController { + func share(url: URL) { + let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) + + present(activityViewController, animated: true, completion: nil) + } + func sizeTableHeaderFooterViews() { // https://useyourloaf.com/blog/variable-height-table-view-header/ if let headerView = tableView.tableHeaderView { diff --git a/ViewModels/Sources/ViewModels/StatusListViewModel.swift b/ViewModels/Sources/ViewModels/StatusListViewModel.swift index 13d8938..8e369cb 100644 --- a/ViewModels/Sources/ViewModels/StatusListViewModel.swift +++ b/ViewModels/Sources/ViewModels/StatusListViewModel.swift @@ -9,15 +9,18 @@ public final class StatusListViewModel: ObservableObject { @Published public private(set) var statusIDs = [[String]]() @Published public var alertItem: AlertItem? @Published public private(set) var loading = false + public let statusEvents: AnyPublisher public private(set) var maintainScrollPositionOfStatusID: String? private var statuses = [String: Status]() private let statusListService: StatusListService private var statusViewModelCache = [Status: (StatusViewModel, AnyCancellable)]() + private let statusEventsSubject = PassthroughSubject() private var cancellables = Set() init(statusListService: StatusListService) { self.statusListService = statusListService + statusEvents = statusEventsSubject.eraseToAnyPublisher() statusListService.statusSections .combineLatest(statusListService.filters.map { $0.regularExpression() }) @@ -54,15 +57,16 @@ public extension StatusListViewModel { guard let status = statuses[id] else { return nil } var statusViewModel: StatusViewModel - + if let cachedViewModel = statusViewModelCache[status]?.0 { statusViewModel = cachedViewModel } else { statusViewModel = StatusViewModel(statusService: statusListService.statusService(status: status)) - statusViewModelCache[status] = (statusViewModel, statusViewModel.events - .flatMap { $0 } - .assignErrorsToAlertItem(to: \.alertItem, on: self) - .sink { _ in }) + statusViewModelCache[status] = (statusViewModel, + statusViewModel.events + .flatMap { $0 } + .assignErrorsToAlertItem(to: \.alertItem, on: self) + .sink { [weak self] in self?.statusEventsSubject.send($0) }) } statusViewModel.isContextParent = status.id == statusListService.contextParentID diff --git a/ViewModels/Sources/ViewModels/StatusViewModel.swift b/ViewModels/Sources/ViewModels/StatusViewModel.swift index bdf9706..1417682 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 eventsInput = PassthroughSubject, Never>() + private let eventsSubject = PassthroughSubject, Never>() init(statusService: StatusService) { self.statusService = statusService @@ -45,7 +45,16 @@ public struct StatusViewModel { .map(AttachmentViewModel.init(attachment:)) pollOptionTitles = statusService.status.displayStatus.poll?.options.map { $0.title } ?? [] pollEmoji = statusService.status.displayStatus.poll?.emojis ?? [] - events = eventsInput.eraseToAnyPublisher() + events = eventsSubject.eraseToAnyPublisher() + } +} + +public extension StatusViewModel { + enum Event { + case ignorableOutput + case statusListNavigation(StatusListViewModel) + case urlNavigation(URL) + case share(URL) } } @@ -108,7 +117,13 @@ public extension StatusViewModel { } func toggleFavorited() { - eventsInput.send(statusService.toggleFavorited()) + eventsSubject.send(statusService.toggleFavorited().map { _ in Event.ignorableOutput }.eraseToAnyPublisher()) + } + + func shareStatus() { + guard let url = statusService.status.displayStatus.url else { return } + + eventsSubject.send(Just(Event.share(url)).setFailureType(to: Error.self).eraseToAnyPublisher()) } } diff --git a/Views/Status/StatusView.swift b/Views/Status/StatusView.swift index aeba6a3..58fadf6 100644 --- a/Views/Status/StatusView.swift +++ b/Views/Status/StatusView.swift @@ -148,6 +148,10 @@ private extension StatusView { favoriteButton.addAction(favoriteAction, for: .touchUpInside) contextParentFavoriteButton.addAction(favoriteAction, for: .touchUpInside) + let shareAction = UIAction { [weak self] _ in self?.statusConfiguration.viewModel.shareStatus() } + + shareButton.addAction(shareAction, for: .touchUpInside) + applyStatusConfiguration() }