chore: cherry pick 00eddc2aae
from feature-post-edit branch
This commit is contained in:
parent
060aec6bcb
commit
0b0d7fcd48
|
@ -12,7 +12,7 @@ import MastodonCore
|
|||
|
||||
extension DataSourceFacade {
|
||||
public static func responseToStatusBookmarkAction(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
provider: UIViewController & NeedsDependency & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
|
|
|
@ -36,7 +36,7 @@ extension DataSourceFacade {
|
|||
button: UIButton
|
||||
) async throws {
|
||||
let activityViewController = try await createActivityViewController(
|
||||
provider: provider,
|
||||
dependency: provider,
|
||||
status: status
|
||||
)
|
||||
provider.coordinator.present(
|
||||
|
@ -51,19 +51,19 @@ extension DataSourceFacade {
|
|||
}
|
||||
|
||||
private static func createActivityViewController(
|
||||
provider: DataSourceProvider,
|
||||
dependency: NeedsDependency,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async throws -> UIActivityViewController {
|
||||
var activityItems: [Any] = try await provider.context.managedObjectContext.perform {
|
||||
guard let status = status.object(in: provider.context.managedObjectContext) else { return [] }
|
||||
var activityItems: [Any] = try await dependency.context.managedObjectContext.perform {
|
||||
guard let status = status.object(in: dependency.context.managedObjectContext) else { return [] }
|
||||
let url = status.url ?? status.uri
|
||||
return [URL(string: url)].compactMap { $0 } as [Any]
|
||||
}
|
||||
var applicationActivities: [UIActivity] = [
|
||||
SafariActivity(sceneCoordinator: provider.coordinator), // open URL
|
||||
SafariActivity(sceneCoordinator: dependency.coordinator), // open URL
|
||||
]
|
||||
|
||||
if let provider = provider as? ShareActivityProvider {
|
||||
if let provider = dependency as? ShareActivityProvider {
|
||||
activityItems.append(contentsOf: provider.activities)
|
||||
applicationActivities.append(contentsOf: provider.applicationActivities)
|
||||
}
|
||||
|
@ -247,6 +247,37 @@ extension DataSourceFacade {
|
|||
from: dependency,
|
||||
transition: .activityViewControllerPresent(animated: true, completion: nil)
|
||||
)
|
||||
case .bookmarkStatus:
|
||||
Task {
|
||||
guard let status = menuContext.status else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
try await DataSourceFacade.responseToStatusBookmarkAction(
|
||||
provider: dependency,
|
||||
status: status
|
||||
)
|
||||
} // end Task
|
||||
case .shareStatus:
|
||||
Task {
|
||||
guard let status = menuContext.status else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
let activityViewController = try await DataSourceFacade.createActivityViewController(
|
||||
dependency: dependency,
|
||||
status: status
|
||||
)
|
||||
await dependency.coordinator.present(
|
||||
scene: .activityViewController(
|
||||
activityViewController: activityViewController,
|
||||
sourceView: menuContext.button,
|
||||
barButtonItem: menuContext.barButtonItem
|
||||
),
|
||||
from: dependency,
|
||||
transition: .activityViewControllerPresent(animated: true, completion: nil)
|
||||
)
|
||||
} // end Task
|
||||
case .deleteStatus:
|
||||
let alertController = UIAlertController(
|
||||
title: "Delete Post",
|
||||
|
|
|
@ -144,7 +144,8 @@ extension NotificationView.ViewModel {
|
|||
name: name,
|
||||
isMuting: isMuting,
|
||||
isBlocking: isBlocking,
|
||||
isMyself: isMyself
|
||||
isMyself: isMyself,
|
||||
isBookmarking: false // no bookmark action display for notification item
|
||||
)
|
||||
notificationView.menuButton.menu = notificationView.setupAuthorMenu(menuContext: menuContext)
|
||||
notificationView.menuButton.showsMenuAsPrimaryAction = true
|
||||
|
|
|
@ -514,13 +514,6 @@ extension StatusView.ViewModel {
|
|||
)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
$isBookmark
|
||||
.sink { isHighlighted in
|
||||
statusView.actionToolbarContainer.configureBookmark(
|
||||
isHighlighted: isHighlighted
|
||||
)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
private func bindMetric(statusView: StatusView) {
|
||||
|
@ -577,13 +570,24 @@ extension StatusView.ViewModel {
|
|||
}
|
||||
|
||||
private func bindMenu(statusView: StatusView) {
|
||||
Publishers.CombineLatest4(
|
||||
let publisherOne = Publishers.CombineLatest(
|
||||
$authorName,
|
||||
$isMuting,
|
||||
$isBlocking,
|
||||
$isMyself
|
||||
)
|
||||
.sink { authorName, isMuting, isBlocking, isMyself in
|
||||
let publishersTwo = Publishers.CombineLatest3(
|
||||
$isMuting,
|
||||
$isBlocking,
|
||||
$isBookmark
|
||||
)
|
||||
|
||||
Publishers.CombineLatest(
|
||||
publisherOne.eraseToAnyPublisher(),
|
||||
publishersTwo.eraseToAnyPublisher()
|
||||
).eraseToAnyPublisher()
|
||||
.sink { tupleOne, tupleTwo in
|
||||
let (authorName, isMyself) = tupleOne
|
||||
let (isMuting, isBlocking, isBookmark) = tupleTwo
|
||||
|
||||
guard let name = authorName?.string else {
|
||||
statusView.menuButton.menu = nil
|
||||
return
|
||||
|
@ -593,7 +597,8 @@ extension StatusView.ViewModel {
|
|||
name: name,
|
||||
isMuting: isMuting,
|
||||
isBlocking: isBlocking,
|
||||
isMyself: isMyself
|
||||
isMyself: isMyself,
|
||||
isBookmarking: isBookmark
|
||||
)
|
||||
statusView.menuButton.menu = statusView.setupAuthorMenu(menuContext: menuContext)
|
||||
statusView.menuButton.showsMenuAsPrimaryAction = true
|
||||
|
|
|
@ -705,6 +705,7 @@ extension StatusView {
|
|||
public let isMuting: Bool
|
||||
public let isBlocking: Bool
|
||||
public let isMyself: Bool
|
||||
public let isBookmarking: Bool
|
||||
}
|
||||
|
||||
public func setupAuthorMenu(menuContext: AuthorMenuContext) -> UIMenu {
|
||||
|
@ -722,6 +723,10 @@ extension StatusView {
|
|||
.reportUser(
|
||||
.init(name: menuContext.name)
|
||||
),
|
||||
.bookmarkStatus(
|
||||
.init(isBookmarking: menuContext.isBookmarking)
|
||||
),
|
||||
.shareStatus
|
||||
]
|
||||
|
||||
if menuContext.isMyself {
|
||||
|
|
|
@ -22,14 +22,11 @@ public final class ActionToolbarContainer: UIView {
|
|||
static let reblogImage = Asset.Arrow.repeat.image.withRenderingMode(.alwaysTemplate)
|
||||
static let starImage = Asset.ObjectsAndTools.star.image.withRenderingMode(.alwaysTemplate)
|
||||
static let starFillImage = Asset.ObjectsAndTools.starFill.image.withRenderingMode(.alwaysTemplate)
|
||||
static let bookmarkImage = Asset.ObjectsAndTools.bookmark.image.withRenderingMode(.alwaysTemplate)
|
||||
static let bookmarkFillImage = Asset.ObjectsAndTools.bookmarkFill.image.withRenderingMode(.alwaysTemplate)
|
||||
static let shareImage = Asset.Communication.share.image.withRenderingMode(.alwaysTemplate)
|
||||
static let shareImage = Asset.Arrow.squareAndArrowUp.image.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
public let replyButton = HighlightDimmableButton()
|
||||
public let reblogButton = HighlightDimmableButton()
|
||||
public let favoriteButton = HighlightDimmableButton()
|
||||
public let bookmarkButton = HighlightDimmableButton()
|
||||
public let shareButton = HighlightDimmableButton()
|
||||
|
||||
public weak var delegate: ActionToolbarContainerDelegate?
|
||||
|
@ -64,7 +61,6 @@ extension ActionToolbarContainer {
|
|||
replyButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
||||
reblogButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
||||
favoriteButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
||||
bookmarkButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
||||
shareButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
|
@ -79,7 +75,7 @@ extension ActionToolbarContainer {
|
|||
subview.removeFromSuperview()
|
||||
}
|
||||
|
||||
let buttons = [replyButton, reblogButton, favoriteButton, bookmarkButton, shareButton]
|
||||
let buttons = [replyButton, reblogButton, favoriteButton, shareButton]
|
||||
buttons.forEach { button in
|
||||
button.tintColor = Asset.Colors.Button.actionToolbar.color
|
||||
button.titleLabel?.font = .monospacedDigitSystemFont(ofSize: 12, weight: .regular)
|
||||
|
@ -94,7 +90,6 @@ extension ActionToolbarContainer {
|
|||
replyButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reply
|
||||
reblogButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reblog // needs update to follow state
|
||||
favoriteButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.favorite // needs update to follow state
|
||||
bookmarkButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.bookmark // needs update to follow state
|
||||
shareButton.accessibilityLabel = L10n.Common.Controls.Actions.share
|
||||
|
||||
switch style {
|
||||
|
@ -105,7 +100,6 @@ extension ActionToolbarContainer {
|
|||
replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal)
|
||||
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
|
||||
favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal)
|
||||
bookmarkButton.setImage(ActionToolbarContainer.bookmarkImage, for: .normal)
|
||||
shareButton.setImage(ActionToolbarContainer.shareImage, for: .normal)
|
||||
|
||||
container.axis = .horizontal
|
||||
|
@ -114,22 +108,18 @@ extension ActionToolbarContainer {
|
|||
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
reblogButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
favoriteButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
bookmarkButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addArrangedSubview(replyButton)
|
||||
container.addArrangedSubview(reblogButton)
|
||||
container.addArrangedSubview(favoriteButton)
|
||||
container.addArrangedSubview(bookmarkButton)
|
||||
container.addArrangedSubview(shareButton)
|
||||
NSLayoutConstraint.activate([
|
||||
replyButton.heightAnchor.constraint(equalToConstant: 36).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: reblogButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: favoriteButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: bookmarkButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).priority(.defaultHigh),
|
||||
replyButton.widthAnchor.constraint(equalTo: reblogButton.widthAnchor).priority(.defaultHigh),
|
||||
replyButton.widthAnchor.constraint(equalTo: favoriteButton.widthAnchor).priority(.defaultHigh),
|
||||
replyButton.widthAnchor.constraint(equalTo: bookmarkButton.widthAnchor).priority(.defaultHigh),
|
||||
])
|
||||
shareButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
shareButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
|
@ -141,7 +131,6 @@ extension ActionToolbarContainer {
|
|||
replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal)
|
||||
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
|
||||
favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal)
|
||||
bookmarkButton.setImage(ActionToolbarContainer.bookmarkImage, for: .normal)
|
||||
|
||||
container.axis = .horizontal
|
||||
container.spacing = 8
|
||||
|
@ -150,7 +139,6 @@ extension ActionToolbarContainer {
|
|||
container.addArrangedSubview(replyButton)
|
||||
container.addArrangedSubview(reblogButton)
|
||||
container.addArrangedSubview(favoriteButton)
|
||||
container.addArrangedSubview(bookmarkButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,11 +185,6 @@ extension ActionToolbarContainer {
|
|||
favoriteButton.setTitleColor(tintColor, for: .highlighted)
|
||||
}
|
||||
|
||||
private func isBookmarkButtonHighlightStateDidChange(to isHighlight: Bool) {
|
||||
let tintColor = isHighlight ? Asset.Colors.brand.color : Asset.Colors.Button.actionToolbar.color
|
||||
bookmarkButton.tintColor = tintColor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ActionToolbarContainer {
|
||||
|
@ -214,7 +197,6 @@ extension ActionToolbarContainer {
|
|||
case replyButton: _action = .reply
|
||||
case reblogButton: _action = .reblog
|
||||
case favoriteButton: _action = .like
|
||||
case bookmarkButton: _action = .bookmark
|
||||
case shareButton: _action = .share
|
||||
default: _action = nil
|
||||
}
|
||||
|
@ -275,20 +257,6 @@ extension ActionToolbarContainer {
|
|||
favoriteButton.accessibilityLabel = L10n.Plural.Count.favorite(count)
|
||||
}
|
||||
|
||||
public func configureBookmark(isHighlighted: Bool) {
|
||||
let image = isHighlighted ? ActionToolbarContainer.bookmarkFillImage : ActionToolbarContainer.bookmarkImage
|
||||
bookmarkButton.setImage(image, for: .normal)
|
||||
let tintColor = isHighlighted ? Asset.Colors.brand.color : Asset.Colors.Button.actionToolbar.color
|
||||
bookmarkButton.tintColor = tintColor
|
||||
|
||||
if isHighlighted {
|
||||
bookmarkButton.accessibilityTraits.insert(.selected)
|
||||
} else {
|
||||
bookmarkButton.accessibilityTraits.remove(.selected)
|
||||
}
|
||||
bookmarkButton.accessibilityLabel = isHighlighted ? L10n.Common.Controls.Status.Actions.unbookmark : L10n.Common.Controls.Status.Actions.bookmark
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ActionToolbarContainer {
|
||||
|
@ -300,7 +268,7 @@ extension ActionToolbarContainer {
|
|||
|
||||
extension ActionToolbarContainer {
|
||||
public override var accessibilityElements: [Any]? {
|
||||
get { [replyButton, reblogButton, favoriteButton, bookmarkButton, shareButton] }
|
||||
get { [replyButton, reblogButton, favoriteButton, shareButton] }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ extension MastodonMenu {
|
|||
case blockUser(BlockUserActionContext)
|
||||
case reportUser(ReportUserActionContext)
|
||||
case shareUser(ShareUserActionContext)
|
||||
case bookmarkStatus(BookmarkStatusActionContext)
|
||||
case shareStatus
|
||||
case deleteStatus
|
||||
|
||||
func build(delegate: MastodonMenuDelegate) -> UIMenuElement {
|
||||
|
@ -88,6 +90,32 @@ extension MastodonMenu {
|
|||
delegate.menuAction(self)
|
||||
}
|
||||
return shareAction
|
||||
case .bookmarkStatus(let context):
|
||||
let action = UIAction(
|
||||
title: context.isBookmarking ? "Remove Bookmark" : "Bookmark", // TODO: i18n
|
||||
image: context.isBookmarking ? UIImage(systemName: "bookmark.slash.fill") : UIImage(systemName: "bookmark"),
|
||||
identifier: nil,
|
||||
discoverabilityTitle: nil,
|
||||
attributes: [],
|
||||
state: .off
|
||||
) { [weak delegate] _ in
|
||||
guard let delegate = delegate else { return }
|
||||
delegate.menuAction(self)
|
||||
}
|
||||
return action
|
||||
case .shareStatus:
|
||||
let action = UIAction(
|
||||
title: "Share", // TODO: i18n
|
||||
image: UIImage(systemName: "square.and.arrow.up"),
|
||||
identifier: nil,
|
||||
discoverabilityTitle: nil,
|
||||
attributes: [],
|
||||
state: .off
|
||||
) { [weak delegate] _ in
|
||||
guard let delegate = delegate else { return }
|
||||
delegate.menuAction(self)
|
||||
}
|
||||
return action
|
||||
case .deleteStatus:
|
||||
let deleteAction = UIAction(
|
||||
title: L10n.Common.Controls.Actions.delete,
|
||||
|
@ -100,7 +128,7 @@ extension MastodonMenu {
|
|||
guard let delegate = delegate else { return }
|
||||
delegate.menuAction(self)
|
||||
}
|
||||
return deleteAction
|
||||
return UIMenu(options: .displayInline, children: [deleteAction])
|
||||
} // end switch
|
||||
} // end func build
|
||||
} // end enum Action
|
||||
|
@ -127,6 +155,14 @@ extension MastodonMenu {
|
|||
}
|
||||
}
|
||||
|
||||
public struct BookmarkStatusActionContext {
|
||||
public let isBookmarking: Bool
|
||||
|
||||
public init(isBookmarking: Bool) {
|
||||
self.isBookmarking = isBookmarking
|
||||
}
|
||||
}
|
||||
|
||||
public struct ReportUserActionContext {
|
||||
public let name: String
|
||||
|
||||
|
|
Loading…
Reference in New Issue