chore: cherry pick 00eddc2aae from feature-post-edit branch

This commit is contained in:
CMK 2022-09-15 21:28:40 +08:00
parent 060aec6bcb
commit 0b0d7fcd48
7 changed files with 102 additions and 56 deletions

View File

@ -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()

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 { }
}
}

View File

@ -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