Skip to content

Commit

Permalink
- AccountControllerSpacesGridViewController: fix finding by @jesmrec in
Browse files Browse the repository at this point in the history
#1349, making superfluous "Drag item" accessibility action no longer available for Spaces

- UniversalItemListCell: add support to automatically create and offer accessibility custom actions for button accessories
- OCShare+Interactions: make .offerAcceptAction and .offerDeclineAction available within the framework, make .offerDeclineAction more specific
- OCShare+UniversalItemListCellContentProvider:
	- take advantage of new UniversalItemListCell feature to provide an accessibility custom actions for "Copy to clipboard" for links (fix finding by @jesmrec in #1349) and to no longer offer duplicate actions that are already available via Swipe actions
	- switch to OCShare.offerAcceptAction and OCShare.offerDeclineAction to determine whether to show "Accept" or "Reject"/"Unshare" buttons
- code consolidation and cleanup
  • Loading branch information
felix-schwarz committed Jun 6, 2024
1 parent 68f84fc commit 6677219
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 28 deletions.
1 change: 0 additions & 1 deletion ownCloud/Client/Viewer/DisplayViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,6 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate {
}

var actionBarButtonItem : UIBarButtonItem {
let itemName = item?.name ?? ""
let actionsBarButtonItem = UIBarButtonItem(image: UIImage(named: "more-dots"), style: .plain, target: self, action: #selector(actionsBarButtonPressed))
actionsBarButtonItem.tag = moreButtonTag
actionsBarButtonItem.accessibilityLabel = "Actions".localized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ class AccountControllerSpacesGridViewController: CollectionViewController, ViewC
self?.setCoverView(coverView, layout: .top)
})
}

// Disable dragging of items, so keyboard control does
// not include "Drag Item" in the accessibility actions
// invoked with Tab + Z
defer { // needed so dragInteractionEnabled.didSet is called despite being set in the initializer
dragInteractionEnabled = false
}
}

static func cellLayout(for traitCollection: UITraitCollection) -> CollectionViewSection.CellLayout {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,13 @@ extension OCShare: UniversalItemListCellContentProvider {

if category == .byMe {
if type == .link {
let (_, copyToClipboardAccessory) = cell.makeAccessoryButton(image: OCSymbol.icon(forSymbolName: "list.clipboard"), title: "Copy".localized, accessibilityLabel: "Copy to clipboard".localized, cssSelectors: [.accessory, .copyToClipboard], action: UIAction(handler: { [weak self, weak context] action in
let (_, copyToClipboardAccessory) = cell.makeAccessoryButton(accessibilityLabel: "Copy to clipboard".localized, cssSelectors: [.accessory, .copyToClipboard], provideAccessibilityCustomAction: true, action: OCAction(title: "Copy".localized, icon: OCSymbol.icon(forSymbolName: "list.clipboard"), action: { [weak self, weak context] _, _, done in
if let self {
if self.copyToClipboard(), let presentationViewController = context?.presentationViewController {
_ = NotificationHUDViewController(on: presentationViewController, title: self.name ?? "Public Link".localized, subtitle: "URL was copied to the clipboard".localized)
}
}
done(nil)
}))

var accessories = [ copyToClipboardAccessory ]
Expand Down Expand Up @@ -245,7 +246,6 @@ extension OCShare: UniversalItemListCellContentProvider {

if category == .withMe, let effectiveState {
var accessories: [UICellAccessory] = []
var customActions: [UIAccessibilityCustomAction] = []
let omitLongActions = (effectiveState == .pending) && (UITraitCollection.current.horizontalSizeClass == .compact)

let makeDecisionAction: (_ accept: Bool) -> Void = { [weak self, weak context] accept in
Expand All @@ -255,31 +255,22 @@ extension OCShare: UniversalItemListCellContentProvider {
}
}

if (effectiveState == .pending || effectiveState == .declined) {
if !omitLongActions {
let (_, accessory) = cell.makeAccessoryButton(image: OCSymbol.icon(forSymbolName: "checkmark.circle"), title: "Accept".localized, accessibilityLabel: "Accept share".localized, cssSelectors: [.accessory, .accept], action: UIAction(handler: { _ in makeDecisionAction(true) }
))

accessories.append(accessory)
}

customActions.append(UIAccessibilityCustomAction(name: "Accept".localized, image: OCSymbol.icon(forSymbolName: "checkmark.circle"), actionHandler: { _ in
if !omitLongActions, offerAcceptAction {
let (_, accessory) = cell.makeAccessoryButton(accessibilityLabel: "Accept share".localized, cssSelectors: [.accessory, .accept], provideAccessibilityCustomAction: false, action: OCAction(title: "Accept".localized, icon: OCSymbol.icon(forSymbolName: "checkmark.circle"), action: { _, _, done in
makeDecisionAction(true)
return true
done(nil)
}))
}

if (effectiveState == .pending || effectiveState == .accepted) {
if !omitLongActions {
let (_, accessory) = cell.makeAccessoryButton(image: OCSymbol.icon(forSymbolName: "minus.circle"), title: "Decline".localized, accessibilityLabel: "Decline share".localized, cssSelectors: [.accessory, .decline], action: UIAction(handler: { _ in makeDecisionAction(false) }))

accessories.append(accessory)
}
accessories.append(accessory)
}

customActions.append(UIAccessibilityCustomAction(name: "Decline".localized, image: OCSymbol.icon(forSymbolName: "minus.circle"), actionHandler: { _ in
if !omitLongActions, offerDeclineAction {
let (_, accessory) = cell.makeAccessoryButton(accessibilityLabel: "Decline share".localized, cssSelectors: [.accessory, .decline], provideAccessibilityCustomAction: false, action: OCAction(title: "Decline".localized, icon: OCSymbol.icon(forSymbolName: "minus.circle"), action: { _, _, done in
makeDecisionAction(false)
return true
done(nil)
}))

accessories.append(accessory)
}

if omitLongActions, let menuItems = composeContextMenuItems(in: nil, location: .contextMenuItem, with: context) {
Expand All @@ -293,7 +284,6 @@ extension OCShare: UniversalItemListCellContentProvider {
}

content.accessories = accessories
content.accessibilityCustomActions = customActions
}

_ = updateContent(content)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,8 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell {
self.accessories = []
}
}

updateAccessibilityCustomActions()
}

// Progress
Expand Down Expand Up @@ -738,6 +740,12 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell {
customActions.append(UIAccessibilityCustomAction(name: "Show message".localized, image: OCSymbol.icon(forSymbolName: "exclamationmark.triangle.fill"), target: self, selector: #selector(messageButtonTapped)))
}

// Add accessibility custom actions attached to accessories
for accessory in accessories {
customActions.append(contentsOf: accessory.attachedAccessibilityCustomActions)
}

// Add accessibility custom actions from content
if let contentCustomActions = content?.accessibilityCustomActions {
customActions.append(contentsOf: contentCustomActions)
}
Expand Down Expand Up @@ -875,8 +883,24 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell {
}()

// - Make custom accessory buttons
open func makeAccessoryButton(image: UIImage? = nil, title: String? = nil, accessibilityLabel: String? = nil, cssSelectors: [ThemeCSSSelector]? = [.accessory], action: UIAction? = nil) -> (UIButton, UICellAccessory) {
return UICellAccessory.borderedButton(image: image, title: title, accessibilityLabel: accessibilityLabel, cssSelectors: cssSelectors, action: action)
open func makeAccessoryButton(image: UIImage? = nil, title: String? = nil, accessibilityLabel: String? = nil, cssSelectors: [ThemeCSSSelector]? = [.accessory], action: OCAction, provideAccessibilityCustomAction: Bool = false) -> (UIButton, UICellAccessory) {
var (button, accessory) = UICellAccessory.borderedButton(image: image, title: title, accessibilityLabel: accessibilityLabel, cssSelectors: cssSelectors, action: action.uiAction())

if provideAccessibilityCustomAction {
accessory.attachedAccessibilityCustomActions = [ action.accessibilityCustomAction() ]
}

return (button, accessory)
}

open func makeAccessoryButton(accessibilityLabel: String? = nil, cssSelectors: [ThemeCSSSelector]? = [.accessory], provideAccessibilityCustomAction: Bool = false, action: OCAction) -> (UIButton, UICellAccessory) {
var (button, accessory) = UICellAccessory.borderedButton(image: action.icon, title: action.title, accessibilityLabel: accessibilityLabel, cssSelectors: cssSelectors, action: action.uiAction())

if provideAccessibilityCustomAction {
accessory.attachedAccessibilityCustomActions = [ action.accessibilityCustomAction() ]
}

return (button, accessory)
}

// MARK: - Prepare for reuse
Expand Down Expand Up @@ -963,6 +987,38 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell {
}
}

// MARK: - Accessibility custom actions support
public extension UICellAccessory {
private static let associatedKeyAttachedAccessibilityCustomActions = malloc(1)!

private var containedCustomView: UIView? {
switch self.accessoryType {
case let .customView(customView):
return customView

default: break
}

return nil
}

var attachedAccessibilityCustomActions: [UIAccessibilityCustomAction] {
set {
if let containedCustomView {
objc_setAssociatedObject(containedCustomView, UICellAccessory.associatedKeyAttachedAccessibilityCustomActions, newValue as NSArray, .OBJC_ASSOCIATION_RETAIN)
}
}

get {
if let containedCustomView {
return objc_getAssociatedObject(containedCustomView, UICellAccessory.associatedKeyAttachedAccessibilityCustomActions) as? [UIAccessibilityCustomAction] ?? []
}

return []
}
}
}

// MARK: - Additional CollectionViewCellStyle.StyleOptions
public extension CollectionViewCellStyle.StyleOptionKey {
static let showRevealButton = CollectionViewCellStyle.StyleOptionKey(rawValue: "showRevealButton")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ extension OCShare {
makeDecision(accept: false, context: context)
}

private var offerAcceptAction: Bool {
var offerAcceptAction: Bool {
return ((self.effectiveState == .pending || self.effectiveState == .declined) && (self.category == .withMe))
}

private var offerDeclineAction: Bool {
return (self.effectiveState == .pending || self.effectiveState == .accepted || self.category == .byMe)
var offerDeclineAction: Bool {
return (((self.effectiveState == .pending || self.effectiveState == .accepted) && (self.category == .withMe)) || self.category == .byMe)
}
}

Expand Down

0 comments on commit 6677219

Please sign in to comment.