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 */; };
|
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */; };
|
||||||
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; };
|
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; };
|
||||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.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 */; };
|
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */; };
|
||||||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposingPrefsView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1449,6 +1451,7 @@
|
||||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
||||||
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */,
|
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */,
|
||||||
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
|
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
|
||||||
|
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */,
|
||||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||||
D626494023C122C800612E6E /* Asset Picker */,
|
D626494023C122C800612E6E /* Asset Picker */,
|
||||||
D61959D0241E842400A37B8E /* Draft Cell */,
|
D61959D0241E842400A37B8E /* Draft Cell */,
|
||||||
|
@ -2168,6 +2171,7 @@
|
||||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
|
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
|
||||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||||
|
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */,
|
||||||
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */,
|
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */,
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||||
|
|
|
@ -65,6 +65,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
|
case .mainScene:
|
||||||
|
return "main-scene"
|
||||||
|
|
||||||
case .showConversation,
|
case .showConversation,
|
||||||
.showTimeline,
|
.showTimeline,
|
||||||
.checkNotifications,
|
.checkNotifications,
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.bookmarks</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.bookmarks</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.my-profile</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.my-profile</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-profile</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-profile</string>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.main-scene</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIApplicationSceneManifest</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
|
@ -15,12 +15,15 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
|
private var launchActivity: NSUserActivity?
|
||||||
|
|
||||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
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 }
|
guard let windowScene = scene as? UIWindowScene else { return }
|
||||||
|
|
||||||
|
if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
|
||||||
|
launchActivity = activity
|
||||||
|
}
|
||||||
|
|
||||||
window = UIWindow(windowScene: windowScene)
|
window = UIWindow(windowScene: windowScene)
|
||||||
|
|
||||||
if let report = AppDelegate.pendingCrashReport {
|
if let report = AppDelegate.pendingCrashReport {
|
||||||
|
@ -91,6 +94,14 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
DraftsManager.save()
|
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) {
|
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||||
// Called as the scene transitions from the background to the foreground.
|
// Called as the scene transitions from the background to the foreground.
|
||||||
// Use this method to undo the changes made on entering the background.
|
// 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) {
|
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
|
||||||
let session = session ?? window!.windowScene!.session
|
let session = session ?? window!.windowScene!.session
|
||||||
if LocalData.shared.onboardingComplete {
|
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 {
|
if session.mastodonController == nil {
|
||||||
session.mastodonController = MastodonController.getForAccount(account)
|
session.mastodonController = MastodonController.getForAccount(account)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ class ComposeDrawingViewController: UIViewController {
|
||||||
private(set) var undoBarButtonItem: UIBarButtonItem!
|
private(set) var undoBarButtonItem: UIBarButtonItem!
|
||||||
private(set) var redoBarButtonItem: UIBarButtonItem!
|
private(set) var redoBarButtonItem: UIBarButtonItem!
|
||||||
|
|
||||||
|
private var toolPicker: PKToolPicker!
|
||||||
|
|
||||||
private var initialDrawing: PKDrawing?
|
private var initialDrawing: PKDrawing?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
@ -70,25 +72,19 @@ class ComposeDrawingViewController: UIViewController {
|
||||||
canvasView.topAnchor.constraint(equalTo: view.topAnchor),
|
canvasView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
canvasView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||||
])
|
])
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
|
|
||||||
// todo: should the PKToolPicker be owned by this VC or something else?
|
toolPicker = PKToolPicker()
|
||||||
if let window = parent?.view.window, let toolPicker = PKToolPicker.shared(for: window) {
|
toolPicker.setVisible(true, forFirstResponder: canvasView)
|
||||||
toolPicker.setVisible(true, forFirstResponder: canvasView)
|
toolPicker.addObserver(canvasView)
|
||||||
toolPicker.addObserver(canvasView)
|
toolPicker.addObserver(self)
|
||||||
toolPicker.addObserver(self)
|
|
||||||
|
updateLayout(for: toolPicker)
|
||||||
updateLayout(for: toolPicker)
|
canvasView.becomeFirstResponder()
|
||||||
canvasView.becomeFirstResponder()
|
|
||||||
|
// wait until the next run loop iteration so that the canvas view has become first responder and it's undo manager exists
|
||||||
// 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 {
|
||||||
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: .NSUndoManagerDidUndoChange, object: self.undoManager!)
|
NotificationCenter.default.addObserver(self, selector: #selector(self.updateUndoRedoButtonState), name: .NSUndoManagerDidRedoChange, 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 composePlaceholder: UIViewController!
|
||||||
private var fastAccountSwitcher: FastAccountSwitcherViewController!
|
private var fastAccountSwitcher: FastAccountSwitcherViewController!
|
||||||
|
|
||||||
|
private var fastSwitcherIndicator: FastAccountSwitcherIndicatorView!
|
||||||
|
private var fastSwitcherConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
var selectedTab: Tab {
|
var selectedTab: Tab {
|
||||||
return Tab(rawValue: selectedIndex)!
|
return Tab(rawValue: selectedIndex)!
|
||||||
}
|
}
|
||||||
|
@ -70,9 +73,62 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
tapRecognizer.cancelsTouchesInView = false
|
tapRecognizer.cancelsTouchesInView = false
|
||||||
tabBar.addGestureRecognizer(tapRecognizer)
|
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
|
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) {
|
@objc private func tabBarTapped(_ recognizer: UITapGestureRecognizer) {
|
||||||
fastAccountSwitcher.hide()
|
fastAccountSwitcher.hide()
|
||||||
}
|
}
|
||||||
|
@ -146,13 +202,9 @@ extension MainTabBarViewController {
|
||||||
|
|
||||||
extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
|
extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
|
||||||
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
|
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
|
||||||
let tabBarButtons = tabBar.subviews.filter { String(describing: type(of: $0)).lowercased().contains("button") }
|
guard let myProfileButton = findMyProfileTabBarButton() else {
|
||||||
// sanity check that there is 1 button per VC
|
|
||||||
guard tabBarButtons.count == viewControllers!.count,
|
|
||||||
let myProfileButton = tabBarButtons.last else {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let locationInButton = myProfileButton.convert(point, from: fastAccountSwitcher.view)
|
let locationInButton = myProfileButton.convert(point, from: fastAccountSwitcher.view)
|
||||||
return myProfileButton.bounds.contains(locationInButton)
|
return myProfileButton.bounds.contains(locationInButton)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,9 @@ struct PreferencesView: View {
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.onDrag {
|
||||||
|
let activity = UserActivityManager.mainSceneActivity(accountID: account.id)
|
||||||
|
return NSItemProvider(object: activity)
|
||||||
}
|
}
|
||||||
}.onDelete { (indices: IndexSet) in
|
}.onDelete { (indices: IndexSet) in
|
||||||
var indices = indices
|
var indices = indices
|
||||||
|
|
|
@ -37,6 +37,15 @@ class UserActivityManager {
|
||||||
}
|
}
|
||||||
return LocalData.shared.getAccount(id: id)
|
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
|
// MARK: - New Post
|
||||||
static func newPostActivity(mentioning: Account? = nil, accountID: String) -> NSUserActivity {
|
static func newPostActivity(mentioning: Account? = nil, accountID: String) -> NSUserActivity {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum UserActivityType: String {
|
enum UserActivityType: String {
|
||||||
|
case mainScene = "space.vaccor.Tusker.activity.main-scene"
|
||||||
case newPost = "space.vaccor.Tusker.activity.new-post"
|
case newPost = "space.vaccor.Tusker.activity.new-post"
|
||||||
case checkNotifications = "space.vaccor.Tusker.activity.check-notifications"
|
case checkNotifications = "space.vaccor.Tusker.activity.check-notifications"
|
||||||
case showTimeline = "space.vaccor.Tusker.activity.show-timeline"
|
case showTimeline = "space.vaccor.Tusker.activity.show-timeline"
|
||||||
|
@ -22,6 +23,8 @@ enum UserActivityType: String {
|
||||||
extension UserActivityType {
|
extension UserActivityType {
|
||||||
var handle: (NSUserActivity) -> Void {
|
var handle: (NSUserActivity) -> Void {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .mainScene:
|
||||||
|
fatalError("cannot handle main scene activity")
|
||||||
case .newPost:
|
case .newPost:
|
||||||
return UserActivityManager.handleNewPost
|
return UserActivityManager.handleNewPost
|
||||||
case .checkNotifications:
|
case .checkNotifications:
|
||||||
|
|
|
@ -75,6 +75,9 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(gifPlaybackModeChanged), name: .NSProcessInfoPowerStateDidChange, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(gifPlaybackModeChanged), name: .NSProcessInfoPowerStateDidChange, object: nil)
|
||||||
|
|
||||||
addInteraction(UIContextMenuInteraction(delegate: self))
|
addInteraction(UIContextMenuInteraction(delegate: self))
|
||||||
|
|
||||||
|
isAccessibilityElement = true
|
||||||
|
accessibilityTraits = [.image, .button]
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func preferencesChanged() {
|
@objc private func preferencesChanged() {
|
||||||
|
@ -293,6 +296,13 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
@objc func imagePressed() {
|
@objc func imagePressed() {
|
||||||
showGallery()
|
showGallery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Accessibility
|
||||||
|
|
||||||
|
override func accessibilityActivate() -> Bool {
|
||||||
|
showGallery()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -248,9 +248,11 @@ class AttachmentsContainerView: UIView {
|
||||||
let attachmentView = AttachmentView(attachment: attachments[index], index: index, expectedSize: size)
|
let attachmentView = AttachmentView(attachment: attachments[index], index: index, expectedSize: size)
|
||||||
attachmentView.delegate = delegate
|
attachmentView.delegate = delegate
|
||||||
attachmentView.translatesAutoresizingMaskIntoConstraints = false
|
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 = 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)
|
attachmentViews.add(attachmentView)
|
||||||
return 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)
|
percentLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
addSubview(percentLabel)
|
addSubview(percentLabel)
|
||||||
|
|
||||||
|
accessibilityLabel = option.title
|
||||||
|
|
||||||
if (poll.voted ?? false) || poll.effectiveExpired,
|
if (poll.voted ?? false) || poll.effectiveExpired,
|
||||||
let optionVotes = option.votesCount {
|
let optionVotes = option.votesCount {
|
||||||
let frac = poll.votesCount == 0 ? 0 : CGFloat(optionVotes) / CGFloat(poll.votesCount)
|
let frac = poll.votesCount == 0 ? 0 : CGFloat(optionVotes) / CGFloat(poll.votesCount)
|
||||||
|
let percent = String(format: "%.0f%%", frac * 100)
|
||||||
|
|
||||||
percentLabel.isHidden = false
|
percentLabel.isHidden = false
|
||||||
percentLabel.text = String(format: "%.0f%%", frac * 100)
|
percentLabel.text = percent
|
||||||
|
|
||||||
let fillView = UIView()
|
let fillView = UIView()
|
||||||
fillView.translatesAutoresizingMaskIntoConstraints = false
|
fillView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -64,6 +67,8 @@ class PollOptionView: UIView {
|
||||||
fillView.topAnchor.constraint(equalTo: topAnchor),
|
fillView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
fillView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
fillView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
accessibilityLabel! += ", \(percent)"
|
||||||
}
|
}
|
||||||
|
|
||||||
let minHeightConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
|
let minHeightConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
|
||||||
|
@ -86,6 +91,8 @@ class PollOptionView: UIView {
|
||||||
percentLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
percentLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
|
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
isAccessibilityElement = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
|
|
@ -69,6 +69,8 @@ class PollOptionsView: UIControl {
|
||||||
stack.addArrangedSubview(optionView)
|
stack.addArrangedSubview(optionView)
|
||||||
return optionView
|
return optionView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessibilityElements = options
|
||||||
}
|
}
|
||||||
|
|
||||||
private func selectOption(_ option: PollOptionView) {
|
private func selectOption(_ option: PollOptionView) {
|
||||||
|
|
|
@ -28,7 +28,6 @@ class StatusPollView: UIView {
|
||||||
private var optionsView: PollOptionsView!
|
private var optionsView: PollOptionsView!
|
||||||
private var voteButton: UIButton!
|
private var voteButton: UIButton!
|
||||||
private var infoLabel: UILabel!
|
private var infoLabel: UILabel!
|
||||||
private var options: [PollOptionView] = []
|
|
||||||
|
|
||||||
private var canVote = true
|
private var canVote = true
|
||||||
private var animator: UIViewPropertyAnimator!
|
private var animator: UIViewPropertyAnimator!
|
||||||
|
@ -74,15 +73,14 @@ class StatusPollView: UIView {
|
||||||
voteButton.topAnchor.constraint(equalTo: optionsView.bottomAnchor),
|
voteButton.topAnchor.constraint(equalTo: optionsView.bottomAnchor),
|
||||||
voteButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
voteButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
accessibilityElements = [optionsView!, infoLabel!, voteButton!]
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(status: StatusMO, poll: Poll?) {
|
func updateUI(status: StatusMO, poll: Poll?) {
|
||||||
self.statusID = status.id
|
self.statusID = status.id
|
||||||
self.poll = poll
|
self.poll = poll
|
||||||
|
|
||||||
// remove old options
|
|
||||||
options.forEach { $0.removeFromSuperview() }
|
|
||||||
|
|
||||||
guard let poll = poll else { return }
|
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
|
// 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() }
|
fieldNamesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
fieldValuesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
fieldValuesStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
|
var fieldAccessibilityElements = [Any]()
|
||||||
for field in account.fields {
|
for field in account.fields {
|
||||||
let nameLabel = EmojiLabel()
|
let nameLabel = EmojiLabel()
|
||||||
nameLabel.text = field.name
|
nameLabel.text = field.name
|
||||||
|
@ -155,7 +156,18 @@ class ProfileHeaderView: UIView {
|
||||||
fieldValuesStackView.addArrangedSubview(valueTextView)
|
fieldValuesStackView.addArrangedSubview(valueTextView)
|
||||||
|
|
||||||
nameLabel.heightAnchor.constraint(equalTo: valueTextView.heightAnchor).isActive = true
|
nameLabel.heightAnchor.constraint(equalTo: valueTextView.heightAnchor).isActive = true
|
||||||
|
fieldAccessibilityElements.append(nameLabel)
|
||||||
|
fieldAccessibilityElements.append(valueTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessibilityElements = [
|
||||||
|
displayNameLabel!,
|
||||||
|
usernameLabel!,
|
||||||
|
noteTextView!,
|
||||||
|
] + fieldAccessibilityElements + [
|
||||||
|
moreButton!,
|
||||||
|
pagesSegmentedControl!,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateRelationship() {
|
private func updateRelationship() {
|
||||||
|
|
|
@ -94,8 +94,7 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
|
||||||
collapseButton.layer.masksToBounds = true
|
collapseButton.layer.masksToBounds = true
|
||||||
collapseButton.layer.cornerRadius = 5
|
collapseButton.layer.cornerRadius = 5
|
||||||
|
|
||||||
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
|
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!, pollView!]
|
||||||
attachmentsView.isAccessibilityElement = true
|
|
||||||
|
|
||||||
moreButton.showsMenuAsPrimaryAction = true
|
moreButton.showsMenuAsPrimaryAction = true
|
||||||
|
|
||||||
|
@ -157,8 +156,6 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
|
||||||
cardView.navigationDelegate = navigationDelegate
|
cardView.navigationDelegate = navigationDelegate
|
||||||
|
|
||||||
attachmentsView.updateUI(status: status)
|
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)
|
updateStatusState(status: status)
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,21 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
profileAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
|
profileAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
|
||||||
profileAccessibilityElement.accessibilityFrameInContainerSpace = profileDetailContainerView.convert(profileDetailContainerView.frame, to: 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)
|
contentTextView.defaultFont = .systemFont(ofSize: 18)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
static let relativeDateFormatter: RelativeDateTimeFormatter = {
|
static let relativeDateFormatter: RelativeDateTimeFormatter = {
|
||||||
let formatter = RelativeDateTimeFormatter()
|
let formatter = RelativeDateTimeFormatter()
|
||||||
formatter.dateTimeStyle = .numeric
|
formatter.dateTimeStyle = .numeric
|
||||||
formatter.unitsStyle = .short
|
formatter.unitsStyle = .full
|
||||||
return formatter
|
return formatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
reblogLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reblogLabelPressed)))
|
reblogLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reblogLabelPressed)))
|
||||||
|
|
||||||
accessibilityElements!.insert(reblogLabel!, at: 0)
|
isAccessibilityElement = true
|
||||||
|
|
||||||
// todo: double check this on RTL layouts
|
// todo: double check this on RTL layouts
|
||||||
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
||||||
|
@ -134,7 +134,6 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
private func doUpdateTimestamp(status: StatusMO) {
|
private func doUpdateTimestamp(status: StatusMO) {
|
||||||
timestampLabel.text = status.createdAt.timeAgoString()
|
timestampLabel.text = status.createdAt.timeAgoString()
|
||||||
timestampLabel.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())
|
|
||||||
|
|
||||||
let delay: DispatchTimeInterval?
|
let delay: DispatchTimeInterval?
|
||||||
switch status.createdAt.timeAgo().1 {
|
switch status.createdAt.timeAgo().1 {
|
||||||
|
@ -193,6 +192,38 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
actions: { self.actionsForStatus(status, sourceView: self) }
|
actions: { self.actionsForStatus(status, sourceView: self) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,8 @@ class VisualEffectImageButton: UIControl {
|
||||||
addInteraction(UIContextMenuInteraction(delegate: self))
|
addInteraction(UIContextMenuInteraction(delegate: self))
|
||||||
|
|
||||||
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
|
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
|
||||||
|
|
||||||
|
isAccessibilityElement = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func onTap() {
|
@objc private func onTap() {
|
||||||
|
|
Loading…
Reference in New Issue