Compare commits
9 Commits
806591f5b7
...
ef1db466b9
Author | SHA1 | Date |
---|---|---|
Shadowfacts | ef1db466b9 | |
Shadowfacts | 0566f0ddfa | |
Shadowfacts | f54d4d757f | |
Shadowfacts | fbc5d6eed9 | |
Shadowfacts | 2c4d2ce551 | |
Shadowfacts | bbe260bc9e | |
Shadowfacts | 2fe19a5abe | |
Shadowfacts | feacf576d7 | |
Shadowfacts | ceb58f1d92 |
|
@ -186,6 +186,7 @@
|
|||
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 */; };
|
||||
|
@ -587,6 +588,7 @@
|
|||
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>"; };
|
||||
|
@ -1449,6 +1451,7 @@
|
|||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
||||
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */,
|
||||
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
|
||||
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */,
|
||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||
D626494023C122C800612E6E /* Asset Picker */,
|
||||
D61959D0241E842400A37B8E /* Draft Cell */,
|
||||
|
@ -2168,6 +2171,7 @@
|
|||
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 */,
|
||||
|
|
|
@ -65,6 +65,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
}
|
||||
|
||||
switch type {
|
||||
case .mainScene:
|
||||
return "main-scene"
|
||||
|
||||
case .showConversation,
|
||||
.showTimeline,
|
||||
.checkNotifications,
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
<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>
|
||||
|
|
|
@ -15,12 +15,15 @@ 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 {
|
||||
|
@ -91,6 +94,14 @@ 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.
|
||||
|
@ -123,7 +134,14 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
|
||||
let session = session ?? window!.windowScene!.session
|
||||
if LocalData.shared.onboardingComplete {
|
||||
let account = LocalData.shared.getMostRecentAccount()!
|
||||
let account: LocalData.UserAccountInfo
|
||||
if let activity = launchActivity,
|
||||
let activityAccount = UserActivityManager.getAccount(from: activity) {
|
||||
account = activityAccount
|
||||
} else {
|
||||
account = LocalData.shared.getMostRecentAccount()!
|
||||
}
|
||||
|
||||
if session.mastodonController == nil {
|
||||
session.mastodonController = MastodonController.getForAccount(account)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ class ComposeDrawingViewController: UIViewController {
|
|||
private(set) var undoBarButtonItem: UIBarButtonItem!
|
||||
private(set) var redoBarButtonItem: UIBarButtonItem!
|
||||
|
||||
private var toolPicker: PKToolPicker!
|
||||
|
||||
private var initialDrawing: PKDrawing?
|
||||
|
||||
init() {
|
||||
|
@ -70,25 +72,19 @@ class ComposeDrawingViewController: UIViewController {
|
|||
canvasView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
toolPicker = PKToolPicker()
|
||||
toolPicker.setVisible(true, forFirstResponder: canvasView)
|
||||
toolPicker.addObserver(canvasView)
|
||||
toolPicker.addObserver(self)
|
||||
|
||||
// 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)
|
||||
updateLayout(for: toolPicker)
|
||||
canvasView.becomeFirstResponder()
|
||||
|
||||
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!)
|
||||
}
|
||||
// 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!)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@ 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)!
|
||||
}
|
||||
|
@ -70,9 +73,62 @@ 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()
|
||||
}
|
||||
|
@ -146,13 +202,9 @@ extension MainTabBarViewController {
|
|||
|
||||
extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
|
||||
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
|
||||
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 {
|
||||
guard let myProfileButton = findMyProfileTabBarButton() else {
|
||||
return false
|
||||
}
|
||||
|
||||
let locationInButton = myProfileButton.convert(point, from: fastAccountSwitcher.view)
|
||||
return myProfileButton.bounds.contains(locationInButton)
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@ struct PreferencesView: View {
|
|||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}.onDrag {
|
||||
let activity = UserActivityManager.mainSceneActivity(accountID: account.id)
|
||||
return NSItemProvider(object: activity)
|
||||
}
|
||||
}.onDelete { (indices: IndexSet) in
|
||||
var indices = indices
|
||||
|
|
|
@ -38,6 +38,15 @@ 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
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
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"
|
||||
|
@ -22,6 +23,8 @@ 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:
|
||||
|
|
|
@ -75,6 +75,9 @@ 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() {
|
||||
|
@ -294,6 +297,13 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
showGallery()
|
||||
}
|
||||
|
||||
// MARK: - Accessibility
|
||||
|
||||
override func accessibilityActivate() -> Bool {
|
||||
showGallery()
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate extension AttachmentView {
|
||||
|
|
|
@ -248,9 +248,11 @@ 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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// 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")
|
||||
}
|
||||
|
||||
}
|
|
@ -44,12 +44,15 @@ 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 = String(format: "%.0f%%", frac * 100)
|
||||
percentLabel.text = percent
|
||||
|
||||
let fillView = UIView()
|
||||
fillView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -64,6 +67,8 @@ class PollOptionView: UIView {
|
|||
fillView.topAnchor.constraint(equalTo: topAnchor),
|
||||
fillView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
|
||||
accessibilityLabel! += ", \(percent)"
|
||||
}
|
||||
|
||||
let minHeightConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
|
||||
|
@ -86,6 +91,8 @@ class PollOptionView: UIView {
|
|||
percentLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
|
||||
])
|
||||
|
||||
isAccessibilityElement = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
|
@ -69,6 +69,8 @@ class PollOptionsView: UIControl {
|
|||
stack.addArrangedSubview(optionView)
|
||||
return optionView
|
||||
}
|
||||
|
||||
accessibilityElements = options
|
||||
}
|
||||
|
||||
private func selectOption(_ option: PollOptionView) {
|
||||
|
|
|
@ -28,7 +28,6 @@ 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!
|
||||
|
@ -74,15 +73,14 @@ 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
|
||||
|
|
|
@ -132,6 +132,7 @@ 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
|
||||
|
@ -155,7 +156,18 @@ 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() {
|
||||
|
|
|
@ -94,8 +94,7 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
|
|||
collapseButton.layer.masksToBounds = true
|
||||
collapseButton.layer.cornerRadius = 5
|
||||
|
||||
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
|
||||
attachmentsView.isAccessibilityElement = true
|
||||
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!, pollView!]
|
||||
|
||||
moreButton.showsMenuAsPrimaryAction = true
|
||||
|
||||
|
@ -157,8 +156,6 @@ 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)
|
||||
|
||||
|
|
|
@ -33,7 +33,21 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
|||
|
||||
profileAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
|
||||
profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: self)
|
||||
accessibilityElements = [profileAccessibilityElement!, contentWarningLabel!, collapseButton!, contentTextView!, totalFavoritesButton!, totalReblogsButton!, timestampAndClientLabel!, replyButton!, favoriteButton!, reblogButton!, moreButton!]
|
||||
accessibilityElements = [
|
||||
profileAccessibilityElement!,
|
||||
contentWarningLabel!,
|
||||
collapseButton!,
|
||||
contentTextView!,
|
||||
attachmentsView!,
|
||||
pollView!,
|
||||
totalFavoritesButton!,
|
||||
totalReblogsButton!,
|
||||
timestampAndClientLabel!,
|
||||
replyButton!,
|
||||
favoriteButton!,
|
||||
reblogButton!,
|
||||
moreButton!,
|
||||
]
|
||||
|
||||
contentTextView.defaultFont = .systemFont(ofSize: 18)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
static let relativeDateFormatter: RelativeDateTimeFormatter = {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.dateTimeStyle = .numeric
|
||||
formatter.unitsStyle = .short
|
||||
formatter.unitsStyle = .full
|
||||
return formatter
|
||||
}()
|
||||
|
||||
|
@ -44,7 +44,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
|
||||
reblogLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reblogLabelPressed)))
|
||||
|
||||
accessibilityElements!.insert(reblogLabel!, at: 0)
|
||||
isAccessibilityElement = true
|
||||
|
||||
// todo: double check this on RTL layouts
|
||||
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
||||
|
@ -134,7 +134,6 @@ 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 {
|
||||
|
@ -194,6 +193,38 @@ 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 {
|
||||
|
|
|
@ -55,6 +55,8 @@ class VisualEffectImageButton: UIControl {
|
|||
addInteraction(UIContextMenuInteraction(delegate: self))
|
||||
|
||||
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
|
||||
|
||||
isAccessibilityElement = true
|
||||
}
|
||||
|
||||
@objc private func onTap() {
|
||||
|
|
Loading…
Reference in New Issue