mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 08:10:59 +00:00
Replying
This commit is contained in:
parent
846c7987dc
commit
354242b835
12 changed files with 114 additions and 43 deletions
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,12 +300,24 @@ 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):
|
||||||
|
handle(navigation: navigation)
|
||||||
|
case let .attachment(attachmentViewModel, statusViewModel):
|
||||||
|
present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel)
|
||||||
|
case let .reply(statusViewModel):
|
||||||
|
reply(statusViewModel: statusViewModel)
|
||||||
|
case let .report(reportViewModel):
|
||||||
|
report(reportViewModel: reportViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(navigation: Navigation) {
|
||||||
switch navigation {
|
switch navigation {
|
||||||
case let .collection(collectionService):
|
case let .collection(collectionService):
|
||||||
show(TableViewController(
|
show(TableViewController(
|
||||||
viewModel: CollectionItemsViewModel(
|
viewModel: CollectionItemsViewModel(
|
||||||
collectionService: collectionService,
|
collectionService: collectionService,
|
||||||
identification: identification),
|
identification: identification),
|
||||||
|
rootViewModel: rootViewModel,
|
||||||
identification: identification),
|
identification: identification),
|
||||||
sender: self)
|
sender: self)
|
||||||
case let .profile(profileService):
|
case let .profile(profileService):
|
||||||
|
@ -312,6 +325,7 @@ private extension TableViewController {
|
||||||
viewModel: ProfileViewModel(
|
viewModel: ProfileViewModel(
|
||||||
profileService: profileService,
|
profileService: profileService,
|
||||||
identification: identification),
|
identification: identification),
|
||||||
|
rootViewModel: rootViewModel,
|
||||||
identification: identification),
|
identification: identification),
|
||||||
sender: self)
|
sender: self)
|
||||||
case let .url(url):
|
case let .url(url):
|
||||||
|
@ -321,11 +335,6 @@ private extension TableViewController {
|
||||||
case .webfingerEnd:
|
case .webfingerEnd:
|
||||||
webfingerIndicatorView.stopAnimating()
|
webfingerIndicatorView.stopAnimating()
|
||||||
}
|
}
|
||||||
case let .attachment(attachmentViewModel, statusViewModel):
|
|
||||||
present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel)
|
|
||||||
case let .report(reportViewModel):
|
|
||||||
report(viewModel: reportViewModel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func present(attachmentViewModel: AttachmentViewModel, statusViewModel: StatusViewModel) {
|
func present(attachmentViewModel: AttachmentViewModel, statusViewModel: StatusViewModel) {
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
5
ViewModels/Sources/ViewModels/Entities/Navigation.swift
Normal file
5
ViewModels/Sources/ViewModels/Entities/Navigation.swift
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public typealias Navigation = ServiceLayer.Navigation
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue