Compare commits

..

No commits in common. "ef1db466b9519c1260ab5fbf9a04d9f581700853" and "806591f5b78a8e2c27803f2365d8743ef49a4d3e" have entirely different histories.

20 changed files with 42 additions and 236 deletions

View File

@ -186,7 +186,6 @@
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */; };
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; };
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.swift */; };
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */; };
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */; };
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */; };
@ -588,7 +587,6 @@
D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountAvatarView.swift; sourceTree = "<group>"; };
D679C09E215850EF00DA27FE /* XCBActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActions.swift; sourceTree = "<group>"; };
D67B506C250B291200FAECFB /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastAccountSwitcherIndicatorView.swift; sourceTree = "<group>"; };
D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeAccountDetailView.swift; sourceTree = "<group>"; };
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.swift; sourceTree = "<group>"; };
@ -1451,7 +1449,6 @@
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */,
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */,
D67C57A721E2649B00C3118B /* Account Detail */,
D626494023C122C800612E6E /* Asset Picker */,
D61959D0241E842400A37B8E /* Draft Cell */,
@ -2171,7 +2168,6 @@
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */,
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */,
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,

View File

@ -65,9 +65,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
switch type {
case .mainScene:
return "main-scene"
case .showConversation,
.showTimeline,
.checkNotifications,

View File

@ -69,7 +69,6 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.bookmarks</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.my-profile</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-profile</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.main-scene</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>

View File

@ -15,15 +15,12 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private var launchActivity: NSUserActivity?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let windowScene = scene as? UIWindowScene else { return }
if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
launchActivity = activity
}
window = UIWindow(windowScene: windowScene)
if let report = AppDelegate.pendingCrashReport {
@ -94,14 +91,6 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
DraftsManager.save()
}
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
if let mastodonController = window?.windowScene?.session.mastodonController {
return UserActivityManager.mainSceneActivity(accountID: mastodonController.accountInfo!.id)
} else {
return nil
}
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
@ -134,14 +123,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
let session = session ?? window!.windowScene!.session
if LocalData.shared.onboardingComplete {
let account: LocalData.UserAccountInfo
if let activity = launchActivity,
let activityAccount = UserActivityManager.getAccount(from: activity) {
account = activityAccount
} else {
account = LocalData.shared.getMostRecentAccount()!
}
let account = LocalData.shared.getMostRecentAccount()!
if session.mastodonController == nil {
session.mastodonController = MastodonController.getForAccount(account)
}

View File

@ -23,8 +23,6 @@ class ComposeDrawingViewController: UIViewController {
private(set) var undoBarButtonItem: UIBarButtonItem!
private(set) var redoBarButtonItem: UIBarButtonItem!
private var toolPicker: PKToolPicker!
private var initialDrawing: PKDrawing?
init() {
@ -72,19 +70,25 @@ class ComposeDrawingViewController: UIViewController {
canvasView.topAnchor.constraint(equalTo: view.topAnchor),
canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
toolPicker = PKToolPicker()
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
toolPicker.addObserver(self)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateLayout(for: toolPicker)
canvasView.becomeFirstResponder()
// todo: should the PKToolPicker be owned by this VC or something else?
if let window = parent?.view.window, let toolPicker = PKToolPicker.shared(for: window) {
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
toolPicker.addObserver(self)
// wait until the next run loop iteration so that the canvas view has become first responder and it's undo manager exists
DispatchQueue.main.async {
NotificationCenter.default.addObserver(self, selector: #selector(self.updateUndoRedoButtonState), name: .NSUndoManagerDidUndoChange, object: self.undoManager!)
NotificationCenter.default.addObserver(self, selector: #selector(self.updateUndoRedoButtonState), name: .NSUndoManagerDidRedoChange, object: self.undoManager!)
updateLayout(for: toolPicker)
canvasView.becomeFirstResponder()
// wait until the next run loop iteration so that the canvas view has become first responder and it's undo manager exists
DispatchQueue.main.async {
NotificationCenter.default.addObserver(self, selector: #selector(self.updateUndoRedoButtonState), name: .NSUndoManagerDidUndoChange, object: self.undoManager!)
NotificationCenter.default.addObserver(self, selector: #selector(self.updateUndoRedoButtonState), name: .NSUndoManagerDidRedoChange, object: self.undoManager!)
}
}
}

View File

@ -15,9 +15,6 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
private var composePlaceholder: UIViewController!
private var fastAccountSwitcher: FastAccountSwitcherViewController!
private var fastSwitcherIndicator: FastAccountSwitcherIndicatorView!
private var fastSwitcherConstraints: [NSLayoutConstraint] = []
var selectedTab: Tab {
return Tab(rawValue: selectedIndex)!
}
@ -73,62 +70,9 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
tapRecognizer.cancelsTouchesInView = false
tabBar.addGestureRecognizer(tapRecognizer)
if findMyProfileTabBarButton() != nil {
fastSwitcherIndicator = FastAccountSwitcherIndicatorView()
fastSwitcherIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(fastSwitcherIndicator)
NSLayoutConstraint.activate([
fastSwitcherIndicator.widthAnchor.constraint(equalToConstant: 10),
fastSwitcherIndicator.heightAnchor.constraint(equalToConstant: 12),
])
}
tabBar.isSpringLoaded = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
repositionFastSwitcherIndicator()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
repositionFastSwitcherIndicator()
}
private func repositionFastSwitcherIndicator() {
guard let myProfileButton = findMyProfileTabBarButton() else {
return
}
NSLayoutConstraint.deactivate(fastSwitcherConstraints)
// using interfaceOrientation isn't ideal, but UITabBar buttons may lay out horizontally even in the compact size class
if traitCollection.horizontalSizeClass == .compact && interfaceOrientation.isPortrait {
fastSwitcherConstraints = [
fastSwitcherIndicator.centerYAnchor.constraint(equalTo: myProfileButton.centerYAnchor, constant: -4),
// tab bar button image width is 30
fastSwitcherIndicator.leftAnchor.constraint(equalTo: myProfileButton.centerXAnchor, constant: 15 + 2),
]
} else {
fastSwitcherConstraints = [
fastSwitcherIndicator.centerYAnchor.constraint(equalTo: myProfileButton.centerYAnchor),
fastSwitcherIndicator.trailingAnchor.constraint(equalTo: myProfileButton.trailingAnchor),
]
}
NSLayoutConstraint.activate(fastSwitcherConstraints)
}
private func findMyProfileTabBarButton() -> UIView? {
let tabBarButtons = tabBar.subviews.filter { String(describing: type(of: $0)).lowercased().contains("button") }
// sanity check that there is 1 button per VC
guard tabBarButtons.count == viewControllers!.count,
let myProfileButton = tabBarButtons.last else {
return nil
}
return myProfileButton
}
@objc private func tabBarTapped(_ recognizer: UITapGestureRecognizer) {
fastAccountSwitcher.hide()
}
@ -202,9 +146,13 @@ extension MainTabBarViewController {
extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
guard let myProfileButton = findMyProfileTabBarButton() else {
let tabBarButtons = tabBar.subviews.filter { String(describing: type(of: $0)).lowercased().contains("button") }
// sanity check that there is 1 button per VC
guard tabBarButtons.count == viewControllers!.count,
let myProfileButton = tabBarButtons.last else {
return false
}
let locationInButton = myProfileButton.convert(point, from: fastAccountSwitcher.view)
return myProfileButton.bounds.contains(locationInButton)
}

View File

@ -36,9 +36,6 @@ struct PreferencesView: View {
.foregroundColor(.secondary)
}
}
}.onDrag {
let activity = UserActivityManager.mainSceneActivity(accountID: account.id)
return NSItemProvider(object: activity)
}
}.onDelete { (indices: IndexSet) in
var indices = indices

View File

@ -38,15 +38,6 @@ class UserActivityManager {
return LocalData.shared.getAccount(id: id)
}
// MARK: - Main Scene
static func mainSceneActivity(accountID: String) -> NSUserActivity {
let activity = NSUserActivity(type: .mainScene)
activity.userInfo = [
"accountID": accountID,
]
return activity
}
// MARK: - New Post
static func newPostActivity(mentioning: Account? = nil, accountID: String) -> NSUserActivity {
// todo: update to use managed objects

View File

@ -9,7 +9,6 @@
import Foundation
enum UserActivityType: String {
case mainScene = "space.vaccor.Tusker.activity.main-scene"
case newPost = "space.vaccor.Tusker.activity.new-post"
case checkNotifications = "space.vaccor.Tusker.activity.check-notifications"
case showTimeline = "space.vaccor.Tusker.activity.show-timeline"
@ -23,8 +22,6 @@ enum UserActivityType: String {
extension UserActivityType {
var handle: (NSUserActivity) -> Void {
switch self {
case .mainScene:
fatalError("cannot handle main scene activity")
case .newPost:
return UserActivityManager.handleNewPost
case .checkNotifications:

View File

@ -75,9 +75,6 @@ class AttachmentView: UIImageView, GIFAnimatable {
NotificationCenter.default.addObserver(self, selector: #selector(gifPlaybackModeChanged), name: .NSProcessInfoPowerStateDidChange, object: nil)
addInteraction(UIContextMenuInteraction(delegate: self))
isAccessibilityElement = true
accessibilityTraits = [.image, .button]
}
@objc private func preferencesChanged() {
@ -297,13 +294,6 @@ class AttachmentView: UIImageView, GIFAnimatable {
showGallery()
}
// MARK: - Accessibility
override func accessibilityActivate() -> Bool {
showGallery()
return true
}
}
fileprivate extension AttachmentView {

View File

@ -248,11 +248,9 @@ class AttachmentsContainerView: UIView {
let attachmentView = AttachmentView(attachment: attachments[index], index: index, expectedSize: size)
attachmentView.delegate = delegate
attachmentView.translatesAutoresizingMaskIntoConstraints = false
attachmentView.isAccessibilityElement = true
attachmentView.accessibilityTraits = [.image, .button]
attachmentView.accessibilityLabel = String(format: NSLocalizedString("Attachment %d", comment: "attachment at index accessiblity label"), index + 1)
attachmentView.accessibilityLabel = "Attachment \(index + 1)"
if let desc = attachments[index].description {
attachmentView.accessibilityLabel! += ", \(desc)"
}
attachmentViews.add(attachmentView)
return attachmentView
}

View File

@ -1,30 +0,0 @@
//
// FastAccountSwitchingIndicatorView.swift
// Tusker
//
// Created by Shadowfacts on 6/6/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import UIKit
class FastAccountSwitcherIndicatorView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
tintColor = .lightGray
let up = UIImageView(image: UIImage(systemName: "arrowtriangle.up.fill")!)
up.frame = CGRect(x: 0, y: 0, width: 10, height: 5)
addSubview(up)
let down = UIImageView(image: UIImage(systemName: "arrowtriangle.down.fill")!)
down.frame = CGRect(x: 0, y: 7, width: 10, height: 5)
addSubview(down)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -44,15 +44,12 @@ class PollOptionView: UIView {
percentLabel.setContentHuggingPriority(.required, for: .horizontal)
addSubview(percentLabel)
accessibilityLabel = option.title
if (poll.voted ?? false) || poll.effectiveExpired,
let optionVotes = option.votesCount {
let frac = poll.votesCount == 0 ? 0 : CGFloat(optionVotes) / CGFloat(poll.votesCount)
let percent = String(format: "%.0f%%", frac * 100)
percentLabel.isHidden = false
percentLabel.text = percent
percentLabel.text = String(format: "%.0f%%", frac * 100)
let fillView = UIView()
fillView.translatesAutoresizingMaskIntoConstraints = false
@ -67,8 +64,6 @@ class PollOptionView: UIView {
fillView.topAnchor.constraint(equalTo: topAnchor),
fillView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
accessibilityLabel! += ", \(percent)"
}
let minHeightConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
@ -91,8 +86,6 @@ class PollOptionView: UIView {
percentLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
])
isAccessibilityElement = true
}
required init?(coder: NSCoder) {

View File

@ -69,8 +69,6 @@ class PollOptionsView: UIControl {
stack.addArrangedSubview(optionView)
return optionView
}
accessibilityElements = options
}
private func selectOption(_ option: PollOptionView) {

View File

@ -28,6 +28,7 @@ class StatusPollView: UIView {
private var optionsView: PollOptionsView!
private var voteButton: UIButton!
private var infoLabel: UILabel!
private var options: [PollOptionView] = []
private var canVote = true
private var animator: UIViewPropertyAnimator!
@ -73,14 +74,15 @@ class StatusPollView: UIView {
voteButton.topAnchor.constraint(equalTo: optionsView.bottomAnchor),
voteButton.bottomAnchor.constraint(equalTo: bottomAnchor),
])
accessibilityElements = [optionsView!, infoLabel!, voteButton!]
}
func updateUI(status: StatusMO, poll: Poll?) {
self.statusID = status.id
self.poll = poll
// remove old options
options.forEach { $0.removeFromSuperview() }
guard let poll = poll else { return }
// poll.voted is nil if there is no user (e.g., public timeline), in which case the poll also cannot be voted upon

View File

@ -132,7 +132,6 @@ class ProfileHeaderView: UIView {
fieldNamesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
fieldValuesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
var fieldAccessibilityElements = [Any]()
for field in account.fields {
let nameLabel = EmojiLabel()
nameLabel.text = field.name
@ -156,18 +155,7 @@ class ProfileHeaderView: UIView {
fieldValuesStackView.addArrangedSubview(valueTextView)
nameLabel.heightAnchor.constraint(equalTo: valueTextView.heightAnchor).isActive = true
fieldAccessibilityElements.append(nameLabel)
fieldAccessibilityElements.append(valueTextView)
}
accessibilityElements = [
displayNameLabel!,
usernameLabel!,
noteTextView!,
] + fieldAccessibilityElements + [
moreButton!,
pagesSegmentedControl!,
]
}
private func updateRelationship() {

View File

@ -94,7 +94,8 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
collapseButton.layer.masksToBounds = true
collapseButton.layer.cornerRadius = 5
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!, pollView!]
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
attachmentsView.isAccessibilityElement = true
moreButton.showsMenuAsPrimaryAction = true
@ -156,6 +157,8 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
cardView.navigationDelegate = navigationDelegate
attachmentsView.updateUI(status: status)
attachmentsView.isAccessibilityElement = status.attachments.count > 0
attachmentsView.accessibilityLabel = String(format: NSLocalizedString("%d attachments", comment: "status attachments count accessibility label"), status.attachments.count)
updateStatusState(status: status)

View File

@ -33,21 +33,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
profileAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: self)
accessibilityElements = [
profileAccessibilityElement!,
contentWarningLabel!,
collapseButton!,
contentTextView!,
attachmentsView!,
pollView!,
totalFavoritesButton!,
totalReblogsButton!,
timestampAndClientLabel!,
replyButton!,
favoriteButton!,
reblogButton!,
moreButton!,
]
accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentTextView!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
contentTextView.defaultFont = .systemFont(ofSize: 18)

View File

@ -15,7 +15,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
static let relativeDateFormatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .numeric
formatter.unitsStyle = .full
formatter.unitsStyle = .short
return formatter
}()
@ -44,7 +44,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
reblogLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reblogLabelPressed)))
isAccessibilityElement = true
accessibilityElements!.insert(reblogLabel!, at: 0)
// todo: double check this on RTL layouts
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
@ -134,6 +134,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
private func doUpdateTimestamp(status: StatusMO) {
timestampLabel.text = status.createdAt.timeAgoString()
timestampLabel.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())
let delay: DispatchTimeInterval?
switch status.createdAt.timeAgo().1 {
@ -193,38 +194,6 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
)
}
// MARK: - Accessibility
override var accessibilityLabel: String? {
get {
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
return nil
}
var str = "\(status.account.displayName), \(contentTextView.text ?? "")"
if status.attachments.count > 0 {
// todo: localize me
str += ", \(status.attachments.count) attachments"
}
if status.poll != nil {
str += ", poll"
}
str += ", \(TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date()))"
if let rebloggerID = rebloggerID,
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
str += ", reblogged by \(reblogger.displayName)"
}
return str
}
set {}
}
override func accessibilityActivate() -> Bool {
didSelectCell()
return true
}
}
extension TimelineStatusTableViewCell: SelectableTableViewCell {

View File

@ -55,8 +55,6 @@ class VisualEffectImageButton: UIControl {
addInteraction(UIContextMenuInteraction(delegate: self))
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
isAccessibilityElement = true
}
@objc private func onTap() {