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 { extension DataSourceFacade {
public static func responseToStatusBookmarkAction( public static func responseToStatusBookmarkAction(
provider: DataSourceProvider & AuthContextProvider, provider: UIViewController & NeedsDependency & AuthContextProvider,
status: ManagedObjectRecord<Status> status: ManagedObjectRecord<Status>
) async throws { ) async throws {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()

View File

@ -36,7 +36,7 @@ extension DataSourceFacade {
button: UIButton button: UIButton
) async throws { ) async throws {
let activityViewController = try await createActivityViewController( let activityViewController = try await createActivityViewController(
provider: provider, dependency: provider,
status: status status: status
) )
provider.coordinator.present( provider.coordinator.present(
@ -51,19 +51,19 @@ extension DataSourceFacade {
} }
private static func createActivityViewController( private static func createActivityViewController(
provider: DataSourceProvider, dependency: NeedsDependency,
status: ManagedObjectRecord<Status> status: ManagedObjectRecord<Status>
) async throws -> UIActivityViewController { ) async throws -> UIActivityViewController {
var activityItems: [Any] = try await provider.context.managedObjectContext.perform { var activityItems: [Any] = try await dependency.context.managedObjectContext.perform {
guard let status = status.object(in: provider.context.managedObjectContext) else { return [] } guard let status = status.object(in: dependency.context.managedObjectContext) else { return [] }
let url = status.url ?? status.uri let url = status.url ?? status.uri
return [URL(string: url)].compactMap { $0 } as [Any] return [URL(string: url)].compactMap { $0 } as [Any]
} }
var applicationActivities: [UIActivity] = [ 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) activityItems.append(contentsOf: provider.activities)
applicationActivities.append(contentsOf: provider.applicationActivities) applicationActivities.append(contentsOf: provider.applicationActivities)
} }
@ -247,6 +247,37 @@ extension DataSourceFacade {
from: dependency, from: dependency,
transition: .activityViewControllerPresent(animated: true, completion: nil) 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: case .deleteStatus:
let alertController = UIAlertController( let alertController = UIAlertController(
title: "Delete Post", title: "Delete Post",

View File

@ -144,7 +144,8 @@ extension NotificationView.ViewModel {
name: name, name: name,
isMuting: isMuting, isMuting: isMuting,
isBlocking: isBlocking, isBlocking: isBlocking,
isMyself: isMyself isMyself: isMyself,
isBookmarking: false // no bookmark action display for notification item
) )
notificationView.menuButton.menu = notificationView.setupAuthorMenu(menuContext: menuContext) notificationView.menuButton.menu = notificationView.setupAuthorMenu(menuContext: menuContext)
notificationView.menuButton.showsMenuAsPrimaryAction = true notificationView.menuButton.showsMenuAsPrimaryAction = true

View File

@ -514,13 +514,6 @@ extension StatusView.ViewModel {
) )
} }
.store(in: &disposeBag) .store(in: &disposeBag)
$isBookmark
.sink { isHighlighted in
statusView.actionToolbarContainer.configureBookmark(
isHighlighted: isHighlighted
)
}
.store(in: &disposeBag)
} }
private func bindMetric(statusView: StatusView) { private func bindMetric(statusView: StatusView) {
@ -577,13 +570,24 @@ extension StatusView.ViewModel {
} }
private func bindMenu(statusView: StatusView) { private func bindMenu(statusView: StatusView) {
Publishers.CombineLatest4( let publisherOne = Publishers.CombineLatest(
$authorName, $authorName,
$isMuting,
$isBlocking,
$isMyself $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 { guard let name = authorName?.string else {
statusView.menuButton.menu = nil statusView.menuButton.menu = nil
return return
@ -593,7 +597,8 @@ extension StatusView.ViewModel {
name: name, name: name,
isMuting: isMuting, isMuting: isMuting,
isBlocking: isBlocking, isBlocking: isBlocking,
isMyself: isMyself isMyself: isMyself,
isBookmarking: isBookmark
) )
statusView.menuButton.menu = statusView.setupAuthorMenu(menuContext: menuContext) statusView.menuButton.menu = statusView.setupAuthorMenu(menuContext: menuContext)
statusView.menuButton.showsMenuAsPrimaryAction = true statusView.menuButton.showsMenuAsPrimaryAction = true

View File

@ -705,6 +705,7 @@ extension StatusView {
public let isMuting: Bool public let isMuting: Bool
public let isBlocking: Bool public let isBlocking: Bool
public let isMyself: Bool public let isMyself: Bool
public let isBookmarking: Bool
} }
public func setupAuthorMenu(menuContext: AuthorMenuContext) -> UIMenu { public func setupAuthorMenu(menuContext: AuthorMenuContext) -> UIMenu {
@ -722,6 +723,10 @@ extension StatusView {
.reportUser( .reportUser(
.init(name: menuContext.name) .init(name: menuContext.name)
), ),
.bookmarkStatus(
.init(isBookmarking: menuContext.isBookmarking)
),
.shareStatus
] ]
if menuContext.isMyself { 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 reblogImage = Asset.Arrow.repeat.image.withRenderingMode(.alwaysTemplate)
static let starImage = Asset.ObjectsAndTools.star.image.withRenderingMode(.alwaysTemplate) static let starImage = Asset.ObjectsAndTools.star.image.withRenderingMode(.alwaysTemplate)
static let starFillImage = Asset.ObjectsAndTools.starFill.image.withRenderingMode(.alwaysTemplate) static let starFillImage = Asset.ObjectsAndTools.starFill.image.withRenderingMode(.alwaysTemplate)
static let bookmarkImage = Asset.ObjectsAndTools.bookmark.image.withRenderingMode(.alwaysTemplate) static let shareImage = Asset.Arrow.squareAndArrowUp.image.withRenderingMode(.alwaysTemplate)
static let bookmarkFillImage = Asset.ObjectsAndTools.bookmarkFill.image.withRenderingMode(.alwaysTemplate)
static let shareImage = Asset.Communication.share.image.withRenderingMode(.alwaysTemplate)
public let replyButton = HighlightDimmableButton() public let replyButton = HighlightDimmableButton()
public let reblogButton = HighlightDimmableButton() public let reblogButton = HighlightDimmableButton()
public let favoriteButton = HighlightDimmableButton() public let favoriteButton = HighlightDimmableButton()
public let bookmarkButton = HighlightDimmableButton()
public let shareButton = HighlightDimmableButton() public let shareButton = HighlightDimmableButton()
public weak var delegate: ActionToolbarContainerDelegate? public weak var delegate: ActionToolbarContainerDelegate?
@ -64,7 +61,6 @@ extension ActionToolbarContainer {
replyButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside) replyButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
reblogButton.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) 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) shareButton.addTarget(self, action: #selector(ActionToolbarContainer.buttonDidPressed(_:)), for: .touchUpInside)
} }
@ -79,7 +75,7 @@ extension ActionToolbarContainer {
subview.removeFromSuperview() subview.removeFromSuperview()
} }
let buttons = [replyButton, reblogButton, favoriteButton, bookmarkButton, shareButton] let buttons = [replyButton, reblogButton, favoriteButton, shareButton]
buttons.forEach { button in buttons.forEach { button in
button.tintColor = Asset.Colors.Button.actionToolbar.color button.tintColor = Asset.Colors.Button.actionToolbar.color
button.titleLabel?.font = .monospacedDigitSystemFont(ofSize: 12, weight: .regular) button.titleLabel?.font = .monospacedDigitSystemFont(ofSize: 12, weight: .regular)
@ -94,7 +90,6 @@ extension ActionToolbarContainer {
replyButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reply replyButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reply
reblogButton.accessibilityLabel = L10n.Common.Controls.Status.Actions.reblog // needs update to follow state 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 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 shareButton.accessibilityLabel = L10n.Common.Controls.Actions.share
switch style { switch style {
@ -105,7 +100,6 @@ extension ActionToolbarContainer {
replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal) replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal)
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal) reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal) favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal)
bookmarkButton.setImage(ActionToolbarContainer.bookmarkImage, for: .normal)
shareButton.setImage(ActionToolbarContainer.shareImage, for: .normal) shareButton.setImage(ActionToolbarContainer.shareImage, for: .normal)
container.axis = .horizontal container.axis = .horizontal
@ -114,22 +108,18 @@ extension ActionToolbarContainer {
replyButton.translatesAutoresizingMaskIntoConstraints = false replyButton.translatesAutoresizingMaskIntoConstraints = false
reblogButton.translatesAutoresizingMaskIntoConstraints = false reblogButton.translatesAutoresizingMaskIntoConstraints = false
favoriteButton.translatesAutoresizingMaskIntoConstraints = false favoriteButton.translatesAutoresizingMaskIntoConstraints = false
bookmarkButton.translatesAutoresizingMaskIntoConstraints = false
shareButton.translatesAutoresizingMaskIntoConstraints = false shareButton.translatesAutoresizingMaskIntoConstraints = false
container.addArrangedSubview(replyButton) container.addArrangedSubview(replyButton)
container.addArrangedSubview(reblogButton) container.addArrangedSubview(reblogButton)
container.addArrangedSubview(favoriteButton) container.addArrangedSubview(favoriteButton)
container.addArrangedSubview(bookmarkButton)
container.addArrangedSubview(shareButton) container.addArrangedSubview(shareButton)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
replyButton.heightAnchor.constraint(equalToConstant: 36).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalToConstant: 36).priority(.defaultHigh),
replyButton.heightAnchor.constraint(equalTo: reblogButton.heightAnchor).priority(.defaultHigh), replyButton.heightAnchor.constraint(equalTo: reblogButton.heightAnchor).priority(.defaultHigh),
replyButton.heightAnchor.constraint(equalTo: favoriteButton.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.heightAnchor.constraint(equalTo: shareButton.heightAnchor).priority(.defaultHigh),
replyButton.widthAnchor.constraint(equalTo: reblogButton.widthAnchor).priority(.defaultHigh), replyButton.widthAnchor.constraint(equalTo: reblogButton.widthAnchor).priority(.defaultHigh),
replyButton.widthAnchor.constraint(equalTo: favoriteButton.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.setContentHuggingPriority(.defaultHigh, for: .horizontal)
shareButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) shareButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
@ -141,7 +131,6 @@ extension ActionToolbarContainer {
replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal) replyButton.setImage(ActionToolbarContainer.replyImage, for: .normal)
reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal) reblogButton.setImage(ActionToolbarContainer.reblogImage, for: .normal)
favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal) favoriteButton.setImage(ActionToolbarContainer.starImage, for: .normal)
bookmarkButton.setImage(ActionToolbarContainer.bookmarkImage, for: .normal)
container.axis = .horizontal container.axis = .horizontal
container.spacing = 8 container.spacing = 8
@ -150,7 +139,6 @@ extension ActionToolbarContainer {
container.addArrangedSubview(replyButton) container.addArrangedSubview(replyButton)
container.addArrangedSubview(reblogButton) container.addArrangedSubview(reblogButton)
container.addArrangedSubview(favoriteButton) container.addArrangedSubview(favoriteButton)
container.addArrangedSubview(bookmarkButton)
} }
} }
@ -197,11 +185,6 @@ extension ActionToolbarContainer {
favoriteButton.setTitleColor(tintColor, for: .highlighted) 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 { extension ActionToolbarContainer {
@ -214,7 +197,6 @@ extension ActionToolbarContainer {
case replyButton: _action = .reply case replyButton: _action = .reply
case reblogButton: _action = .reblog case reblogButton: _action = .reblog
case favoriteButton: _action = .like case favoriteButton: _action = .like
case bookmarkButton: _action = .bookmark
case shareButton: _action = .share case shareButton: _action = .share
default: _action = nil default: _action = nil
} }
@ -275,20 +257,6 @@ extension ActionToolbarContainer {
favoriteButton.accessibilityLabel = L10n.Plural.Count.favorite(count) 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 { extension ActionToolbarContainer {
@ -300,7 +268,7 @@ extension ActionToolbarContainer {
extension ActionToolbarContainer { extension ActionToolbarContainer {
public override var accessibilityElements: [Any]? { public override var accessibilityElements: [Any]? {
get { [replyButton, reblogButton, favoriteButton, bookmarkButton, shareButton] } get { [replyButton, reblogButton, favoriteButton, shareButton] }
set { } set { }
} }
} }

View File

@ -32,6 +32,8 @@ extension MastodonMenu {
case blockUser(BlockUserActionContext) case blockUser(BlockUserActionContext)
case reportUser(ReportUserActionContext) case reportUser(ReportUserActionContext)
case shareUser(ShareUserActionContext) case shareUser(ShareUserActionContext)
case bookmarkStatus(BookmarkStatusActionContext)
case shareStatus
case deleteStatus case deleteStatus
func build(delegate: MastodonMenuDelegate) -> UIMenuElement { func build(delegate: MastodonMenuDelegate) -> UIMenuElement {
@ -88,6 +90,32 @@ extension MastodonMenu {
delegate.menuAction(self) delegate.menuAction(self)
} }
return shareAction 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: case .deleteStatus:
let deleteAction = UIAction( let deleteAction = UIAction(
title: L10n.Common.Controls.Actions.delete, title: L10n.Common.Controls.Actions.delete,
@ -100,7 +128,7 @@ extension MastodonMenu {
guard let delegate = delegate else { return } guard let delegate = delegate else { return }
delegate.menuAction(self) delegate.menuAction(self)
} }
return deleteAction return UIMenu(options: .displayInline, children: [deleteAction])
} // end switch } // end switch
} // end func build } // end func build
} // end enum Action } // 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 struct ReportUserActionContext {
public let name: String public let name: String