This commit is contained in:
Justin Mazzocchi 2021-01-09 21:56:15 -08:00
parent 846c7987dc
commit 354242b835
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
12 changed files with 114 additions and 43 deletions

View file

@ -54,4 +54,13 @@ public extension CollectionItem {
public extension CollectionItem.StatusConfiguration { public extension CollectionItem.StatusConfiguration {
static let `default` = Self(showContentToggled: false, showAttachmentsToggled: false) static let `default` = Self(showContentToggled: false, showAttachmentsToggled: false)
func reply() -> Self {
Self(showContentToggled: showContentToggled,
showAttachmentsToggled: showAttachmentsToggled,
isContextParent: false,
isPinned: false,
isReplyInContext: false,
hasReplyFollowing: true)
}
} }

View file

@ -68,6 +68,18 @@ final class NewStatusViewController: UIViewController {
self?.viewModel.post() self?.viewModel.post()
} }
#if !IS_SHARE_EXTENSION
if let inReplyToViewModel = viewModel.inReplyToViewModel {
let statusView = StatusView(configuration: .init(viewModel: inReplyToViewModel))
statusView.isUserInteractionEnabled = false
statusView.bodyView.alpha = 0.5
statusView.buttonsStackView.isHidden = true
stackView.addArrangedSubview(statusView)
}
#endif
setupViewModelBindings() setupViewModelBindings()
} }
@ -145,7 +157,9 @@ private extension NewStatusViewController {
let compositionView = CompositionView( let compositionView = CompositionView(
viewModel: compositionViewModel, viewModel: compositionViewModel,
parentViewModel: viewModel) parentViewModel: viewModel)
stackView.insertArrangedSubview(compositionView, at: index) let adjustedIndex = viewModel.inReplyToViewModel == nil ? index : index + 1
stackView.insertArrangedSubview(compositionView, at: adjustedIndex)
compositionView.textView.becomeFirstResponder() compositionView.textView.becomeFirstResponder()
DispatchQueue.main.async { DispatchQueue.main.async {

View file

@ -9,10 +9,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, identification: Identification) { required init(viewModel: ProfileViewModel, rootViewModel: RootViewModel, identification: Identification) {
self.viewModel = viewModel self.viewModel = viewModel
super.init(viewModel: viewModel, identification: identification) super.init(viewModel: viewModel, rootViewModel: rootViewModel, identification: identification)
} }
override func viewDidLoad() { override func viewDidLoad() {
@ -108,7 +108,7 @@ private extension ProfileViewController {
let reportViewModel = self.viewModel.accountViewModel?.reportViewModel() let reportViewModel = self.viewModel.accountViewModel?.reportViewModel()
else { return } else { return }
self.report(viewModel: reportViewModel) self.report(reportViewModel: reportViewModel)
}) })
if relationship.blocking { if relationship.blocking {

View file

@ -6,10 +6,12 @@ import SafariServices
import SwiftUI import SwiftUI
import ViewModels import ViewModels
// swiftlint:disable file_length
class TableViewController: UITableViewController { class TableViewController: UITableViewController {
var transitionViewTag = -1 var transitionViewTag = -1
private let viewModel: CollectionViewModel private let viewModel: CollectionViewModel
private let rootViewModel: RootViewModel
private let identification: Identification private let identification: Identification
private let loadingTableFooterView = LoadingTableFooterView() private let loadingTableFooterView = LoadingTableFooterView()
private let webfingerIndicatorView = WebfingerIndicatorView() private let webfingerIndicatorView = WebfingerIndicatorView()
@ -21,8 +23,9 @@ class TableViewController: UITableViewController {
.init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:)) .init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:))
}() }()
init(viewModel: CollectionViewModel, identification: Identification) { init(viewModel: CollectionViewModel, rootViewModel: RootViewModel, identification: Identification) {
self.viewModel = viewModel self.viewModel = viewModel
self.rootViewModel = rootViewModel
self.identification = identification self.identification = identification
super.init(style: .plain) super.init(style: .plain)
@ -109,15 +112,6 @@ class TableViewController: UITableViewController {
} }
} }
extension TableViewController {
func report(viewModel: ReportViewModel) {
let reportViewController = ReportViewController(viewModel: viewModel)
let navigationController = UINavigationController(rootViewController: reportViewController)
present(navigationController, animated: true)
}
}
extension TableViewController: UITableViewDataSourcePrefetching { extension TableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
guard guard
@ -132,6 +126,13 @@ extension TableViewController: UITableViewDataSourcePrefetching {
} }
extension TableViewController { extension TableViewController {
func report(reportViewModel: ReportViewModel) {
let reportViewController = ReportViewController(viewModel: reportViewModel)
let navigationController = UINavigationController(rootViewController: reportViewController)
present(navigationController, animated: true)
}
func sizeTableHeaderFooterViews() { func sizeTableHeaderFooterViews() {
// https://useyourloaf.com/blog/variable-height-table-view-header/ // https://useyourloaf.com/blog/variable-height-table-view-header/
if let headerView = tableView.tableHeaderView { if let headerView = tableView.tableHeaderView {
@ -299,32 +300,40 @@ private extension TableViewController {
case let .share(url): case let .share(url):
share(url: url) share(url: url)
case let .navigation(navigation): case let .navigation(navigation):
switch navigation { handle(navigation: 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()
}
case let .attachment(attachmentViewModel, statusViewModel): case let .attachment(attachmentViewModel, statusViewModel):
present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel) present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel)
case let .reply(statusViewModel):
reply(statusViewModel: statusViewModel)
case let .report(reportViewModel): case let .report(reportViewModel):
report(viewModel: reportViewModel) report(reportViewModel: reportViewModel)
}
}
func handle(navigation: Navigation) {
switch navigation {
case let .collection(collectionService):
show(TableViewController(
viewModel: CollectionItemsViewModel(
collectionService: collectionService,
identification: identification),
rootViewModel: rootViewModel,
identification: identification),
sender: self)
case let .profile(profileService):
show(ProfileViewController(
viewModel: ProfileViewModel(
profileService: profileService,
identification: identification),
rootViewModel: rootViewModel,
identification: identification),
sender: self)
case let .url(url):
present(SFSafariViewController(url: url), animated: true)
case .webfingerStart:
webfingerIndicatorView.startAnimating()
case .webfingerEnd:
webfingerIndicatorView.stopAnimating()
} }
} }
@ -365,6 +374,18 @@ private extension TableViewController {
} }
} }
func reply(statusViewModel: StatusViewModel) {
let newStatusViewModel = rootViewModel.newStatusViewModel(
identification: identification,
inReplyTo: statusViewModel)
let newStatusViewController = UIHostingController(rootView: NewStatusView { newStatusViewModel })
let navigationController = UINavigationController(rootViewController: newStatusViewController)
navigationController.modalPresentationStyle = .overFullScreen
present(navigationController, animated: true)
}
func set(expandAllState: ExpandAllState) { func set(expandAllState: ExpandAllState) {
switch expandAllState { switch expandAllState {
case .hidden: case .hidden:
@ -388,3 +409,4 @@ private extension TableViewController {
present(activityViewController, animated: true, completion: nil) present(activityViewController, animated: true, completion: nil)
} }
} }
// swiftlint:enable file_length

View file

@ -7,6 +7,7 @@ public enum CollectionItemEvent {
case ignorableOutput case ignorableOutput
case navigation(Navigation) case navigation(Navigation)
case attachment(AttachmentViewModel, StatusViewModel) case attachment(AttachmentViewModel, StatusViewModel)
case reply(StatusViewModel)
case report(ReportViewModel) case report(ReportViewModel)
case share(URL) case share(URL)
} }

View file

@ -0,0 +1,5 @@
// Copyright © 2020 Metabolist. All rights reserved.
import ServiceLayer
public typealias Navigation = ServiceLayer.Navigation

View file

@ -14,6 +14,7 @@ public final class NewStatusViewModel: ObservableObject {
@Published public var canChangeIdentity = true @Published public var canChangeIdentity = true
@Published public var alertItem: AlertItem? @Published public var alertItem: AlertItem?
@Published public private(set) var postingState = PostingState.composing @Published public private(set) var postingState = PostingState.composing
public let inReplyToViewModel: StatusViewModel?
public let events: AnyPublisher<Event, Never> public let events: AnyPublisher<Event, Never>
private let allIdentitiesService: AllIdentitiesService private let allIdentitiesService: AllIdentitiesService
@ -24,10 +25,12 @@ public final class NewStatusViewModel: ObservableObject {
public init(allIdentitiesService: AllIdentitiesService, public init(allIdentitiesService: AllIdentitiesService,
identification: Identification, identification: Identification,
environment: AppEnvironment) { environment: AppEnvironment,
inReplyTo: StatusViewModel?) {
self.allIdentitiesService = allIdentitiesService self.allIdentitiesService = allIdentitiesService
self.identification = identification self.identification = identification
self.environment = environment self.environment = environment
inReplyToViewModel = inReplyTo
compositionViewModels = [CompositionViewModel(eventsSubject: compositionEventsSubject)] compositionViewModels = [CompositionViewModel(eventsSubject: compositionEventsSubject)]
events = eventsSubject.eraseToAnyPublisher() events = eventsSubject.eraseToAnyPublisher()
visibility = identification.identity.preferences.postingDefaultVisibility visibility = identification.identity.preferences.postingDefaultVisibility
@ -109,7 +112,7 @@ public extension NewStatusViewModel {
func post() { func post() {
guard let unposted = compositionViewModels.first(where: { !$0.isPosted }) else { return } guard let unposted = compositionViewModels.first(where: { !$0.isPosted }) else { return }
post(viewModel: unposted, inReplyToId: nil) post(viewModel: unposted, inReplyToId: inReplyToViewModel?.id)
} }
} }

View file

@ -58,11 +58,12 @@ public extension RootViewModel {
instanceURLService: InstanceURLService(environment: environment)) instanceURLService: InstanceURLService(environment: environment))
} }
func newStatusViewModel(identification: Identification) -> NewStatusViewModel { func newStatusViewModel(identification: Identification, inReplyTo: StatusViewModel? = nil) -> NewStatusViewModel {
NewStatusViewModel( NewStatusViewModel(
allIdentitiesService: allIdentitiesService, allIdentitiesService: allIdentitiesService,
identification: identification, identification: identification,
environment: environment) environment: environment,
inReplyTo: inReplyTo)
} }
} }

View file

@ -36,6 +36,7 @@ public extension ShareExtensionNavigationViewModel {
return NewStatusViewModel( return NewStatusViewModel(
allIdentitiesService: allIdentitiesService, allIdentitiesService: allIdentitiesService,
identification: identification, identification: identification,
environment: environment) environment: environment,
inReplyTo: nil)
} }
} }

View file

@ -72,6 +72,8 @@ public extension StatusViewModel {
sensitive || identification.identity.preferences.readingExpandMedia == .hideAll sensitive || identification.identity.preferences.readingExpandMedia == .hideAll
} }
var id: Status.Id { statusService.status.displayStatus.id }
var accountName: String { "@".appending(statusService.status.displayStatus.account.acct) } var accountName: String { "@".appending(statusService.status.displayStatus.account.acct) }
var avatarURL: URL { var avatarURL: URL {
@ -200,6 +202,14 @@ public extension StatusViewModel {
.eraseToAnyPublisher()) .eraseToAnyPublisher())
} }
func reply() {
let replyViewModel = Self(statusService: statusService, identification: identification)
replyViewModel.configuration = configuration.reply()
eventsSubject.send(Just(.reply(replyViewModel)).setFailureType(to: Error.self).eraseToAnyPublisher())
}
func toggleReblogged() { func toggleReblogged() {
eventsSubject.send( eventsSubject.send(
statusService.toggleReblogged() statusService.toggleReblogged()

View file

@ -198,6 +198,10 @@ private extension StatusView {
interactionsStackView.addArrangedSubview(favoritedByButton) interactionsStackView.addArrangedSubview(favoritedByButton)
interactionsStackView.distribution = .fillEqually interactionsStackView.distribution = .fillEqually
replyButton.addAction(
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.reply() },
for: .touchUpInside)
reblogButton.addAction( reblogButton.addAction(
UIAction { [weak self] _ in self?.statusConfiguration.viewModel.toggleReblogged() }, UIAction { [weak self] _ in self?.statusConfiguration.viewModel.toggleReblogged() },
for: .touchUpInside) for: .touchUpInside)

View file

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