Compare commits
No commits in common. "cf870916c9b477726c2a4516828c6dc1cbad550f" and "a5506aeab6e89e45765f9df7041bf962e8474f04" have entirely different histories.
cf870916c9
...
a5506aeab6
|
@ -29,11 +29,11 @@ public protocol DuckableViewControllerDelegate: AnyObject {
|
||||||
|
|
||||||
extension UIViewController {
|
extension UIViewController {
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
public func presentDuckable(_ viewController: DuckableViewController, animated: Bool, isDucked: Bool = false) -> Bool {
|
public func presentDuckable(_ viewController: DuckableViewController) -> Bool {
|
||||||
var cur: UIViewController? = self
|
var cur: UIViewController? = self
|
||||||
while let vc = cur {
|
while let vc = cur {
|
||||||
if let container = vc as? DuckableContainerViewController {
|
if let container = vc as? DuckableContainerViewController {
|
||||||
container.presentDuckable(viewController, animated: animated, isDucked: isDucked, completion: nil)
|
container.presentDuckable(viewController, animated: true, completion: nil)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
cur = vc.parent
|
cur = vc.parent
|
||||||
|
|
|
@ -17,14 +17,6 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
||||||
private var bottomConstraint: NSLayoutConstraint!
|
private var bottomConstraint: NSLayoutConstraint!
|
||||||
private(set) var state = State.idle
|
private(set) var state = State.idle
|
||||||
|
|
||||||
public var duckedViewController: DuckableViewController? {
|
|
||||||
if case .ducked(let vc, placeholder: _) = state {
|
|
||||||
return vc
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(child: UIViewController) {
|
public init(child: UIViewController) {
|
||||||
self.child = child
|
self.child = child
|
||||||
|
|
||||||
|
@ -58,7 +50,7 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentDuckable(_ viewController: DuckableViewController, animated: Bool, isDucked: Bool, completion: (() -> Void)?) {
|
func presentDuckable(_ viewController: DuckableViewController, animated: Bool, completion: (() -> Void)?) {
|
||||||
guard case .idle = state else {
|
guard case .idle = state else {
|
||||||
if animated,
|
if animated,
|
||||||
case .ducked(_, placeholder: let placeholder) = state {
|
case .ducked(_, placeholder: let placeholder) = state {
|
||||||
|
@ -77,13 +69,8 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isDucked {
|
state = .presentingDucked(viewController, isFirstPresentation: true)
|
||||||
state = .ducked(viewController, placeholder: createPlaceholderForDuckedViewController(viewController))
|
doPresentDuckable(viewController, animated: animated, completion: completion)
|
||||||
configureChildForDuckedPlaceholder()
|
|
||||||
} else {
|
|
||||||
state = .presentingDucked(viewController, isFirstPresentation: true)
|
|
||||||
doPresentDuckable(viewController, animated: animated, completion: completion)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doPresentDuckable(_ viewController: DuckableViewController, animated: Bool, completion: (() -> Void)?) {
|
private func doPresentDuckable(_ viewController: DuckableViewController, animated: Bool, completion: (() -> Void)?) {
|
||||||
|
@ -92,7 +79,9 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
||||||
nav.modalPresentationStyle = .custom
|
nav.modalPresentationStyle = .custom
|
||||||
nav.transitioningDelegate = self
|
nav.transitioningDelegate = self
|
||||||
present(nav, animated: animated) {
|
present(nav, animated: animated) {
|
||||||
self.configureChildForDuckedPlaceholder()
|
self.bottomConstraint.isActive = false
|
||||||
|
self.bottomConstraint = self.child.view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -detentHeight - 4)
|
||||||
|
self.bottomConstraint.isActive = true
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,18 +127,10 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
||||||
}
|
}
|
||||||
let placeholder = createPlaceholderForDuckedViewController(viewController)
|
let placeholder = createPlaceholderForDuckedViewController(viewController)
|
||||||
state = .ducked(viewController, placeholder: placeholder)
|
state = .ducked(viewController, placeholder: placeholder)
|
||||||
configureChildForDuckedPlaceholder()
|
|
||||||
dismiss(animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configureChildForDuckedPlaceholder() {
|
|
||||||
bottomConstraint.isActive = false
|
|
||||||
bottomConstraint = child.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -detentHeight - 4)
|
|
||||||
bottomConstraint.isActive = true
|
|
||||||
|
|
||||||
child.view.layer.cornerRadius = duckedCornerRadius
|
child.view.layer.cornerRadius = duckedCornerRadius
|
||||||
child.view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
child.view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||||
child.view.layer.masksToBounds = true
|
child.view.layer.masksToBounds = true
|
||||||
|
dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func unduckViewController() {
|
@objc func unduckViewController() {
|
||||||
|
@ -210,10 +191,7 @@ extension DuckableContainerViewController: UIViewControllerTransitioningDelegate
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
extension DuckableContainerViewController: UISheetPresentationControllerDelegate {
|
extension DuckableContainerViewController: UISheetPresentationControllerDelegate {
|
||||||
public func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
|
public func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
|
||||||
guard let snapshot = child.view.snapshotView(afterScreenUpdates: false) else {
|
let snapshot = child.view.snapshotView(afterScreenUpdates: false)!
|
||||||
setOverrideTraitCollection(UITraitCollection(userInterfaceLevel: .elevated), forChild: child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
snapshot.translatesAutoresizingMaskIntoConstraints = false
|
snapshot.translatesAutoresizingMaskIntoConstraints = false
|
||||||
self.view.addSubview(snapshot)
|
self.view.addSubview(snapshot)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
|
|
@ -48,7 +48,6 @@
|
||||||
D61F758A2932E1FC00C0B37F /* SwipeActionsPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */; };
|
D61F758A2932E1FC00C0B37F /* SwipeActionsPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */; };
|
||||||
D61F758D2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */; };
|
D61F758D2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */; };
|
||||||
D61F758E2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61F758C2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib */; };
|
D61F758E2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61F758C2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib */; };
|
||||||
D61F759029353B4300C0B37F /* FileManager+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F758F29353B4300C0B37F /* FileManager+Size.swift */; };
|
|
||||||
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; };
|
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; };
|
||||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
|
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
|
||||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
|
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
|
||||||
|
@ -413,7 +412,6 @@
|
||||||
D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionsPrefsView.swift; sourceTree = "<group>"; };
|
D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionsPrefsView.swift; sourceTree = "<group>"; };
|
||||||
D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusUpdatedNotificationTableViewCell.swift; sourceTree = "<group>"; };
|
D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusUpdatedNotificationTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D61F758C2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusUpdatedNotificationTableViewCell.xib; sourceTree = "<group>"; };
|
D61F758C2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusUpdatedNotificationTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D61F758F29353B4300C0B37F /* FileManager+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Size.swift"; sourceTree = "<group>"; };
|
|
||||||
D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; };
|
D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; };
|
||||||
D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; };
|
D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; };
|
||||||
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
|
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1168,7 +1166,6 @@
|
||||||
D62E9984279CA23900C26176 /* URLSession+Development.swift */,
|
D62E9984279CA23900C26176 /* URLSession+Development.swift */,
|
||||||
D6ADB6ED28EA74E8009924AB /* UIView+Configure.swift */,
|
D6ADB6ED28EA74E8009924AB /* UIView+Configure.swift */,
|
||||||
D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */,
|
D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */,
|
||||||
D61F758F29353B4300C0B37F /* FileManager+Size.swift */,
|
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1938,7 +1935,6 @@
|
||||||
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */,
|
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */,
|
||||||
D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */,
|
D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */,
|
||||||
D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */,
|
D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */,
|
||||||
D61F759029353B4300C0B37F /* FileManager+Size.swift in Sources */,
|
|
||||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||||
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||||
|
|
|
@ -122,10 +122,6 @@ class DiskCache<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSizeInBytes() -> Int64? {
|
|
||||||
return fileManager.recursiveSize(url: URL(fileURLWithPath: path, isDirectory: true))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DiskCache {
|
extension DiskCache {
|
||||||
|
|
|
@ -110,10 +110,6 @@ class ImageCache {
|
||||||
try cache.removeAll()
|
try cache.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDiskSizeInBytes() -> Int64? {
|
|
||||||
return cache.disk?.getSizeInBytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias Request = URLSessionDataTask
|
typealias Request = URLSessionDataTask
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import UIKit
|
||||||
class ImageDataCache {
|
class ImageDataCache {
|
||||||
|
|
||||||
private let memory: MemoryCache<Entry>
|
private let memory: MemoryCache<Entry>
|
||||||
let disk: DiskCache<Data>?
|
private let disk: DiskCache<Data>?
|
||||||
|
|
||||||
private let storeOriginalDataInMemory: Bool
|
private let storeOriginalDataInMemory: Bool
|
||||||
private let desiredPixelSize: CGSize?
|
private let desiredPixelSize: CGSize?
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
//
|
|
||||||
// FileManager+Size.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 11/28/22.
|
|
||||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension FileManager {
|
|
||||||
func recursiveSize(url: URL) -> Int64? {
|
|
||||||
if (try? url.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile) == true {
|
|
||||||
return size(url: url)
|
|
||||||
} else {
|
|
||||||
guard let enumerator = enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey, .fileSizeKey, .totalFileAllocatedSizeKey]) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var total: Int64 = 0
|
|
||||||
for case let url as URL in enumerator {
|
|
||||||
total += size(url: url) ?? 0
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func size(url: URL) -> Int64? {
|
|
||||||
guard let resourceValues = try? url.resourceValues(forKeys: [.isRegularFileKey, .fileSizeKey, .totalFileAllocatedSizeKey]),
|
|
||||||
resourceValues.isRegularFile ?? false,
|
|
||||||
let size = resourceValues.fileSize ?? resourceValues.totalFileAllocatedSize else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return Int64(size)
|
|
||||||
}
|
|
|
@ -97,7 +97,7 @@ private func createFavoriteAction(status: StatusMO, container: StatusSwipeAction
|
||||||
}
|
}
|
||||||
let title = status.favourited ? "Unfavorite" : "Favorite"
|
let title = status.favourited ? "Unfavorite" : "Favorite"
|
||||||
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
|
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
|
||||||
Task { @MainActor in
|
Task {
|
||||||
await FavoriteService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleFavorite()
|
await FavoriteService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleFavorite()
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ private func createReblogAction(status: StatusMO, container: StatusSwipeActionCo
|
||||||
}
|
}
|
||||||
let title = status.reblogged ? "Unreblog" : "Reblog"
|
let title = status.reblogged ? "Unreblog" : "Reblog"
|
||||||
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
|
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
|
||||||
Task { @MainActor in
|
Task {
|
||||||
await ReblogService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleReblog()
|
await ReblogService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleReblog()
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ struct ComposeCurrentAccount: View {
|
||||||
ComposeAvatarImageView(url: account?.avatar)
|
ComposeAvatarImageView(url: account?.avatar)
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
|
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
|
||||||
.accessibilityHidden(true)
|
.accessibility(label: Text(account != nil ? "\(account!.displayName) avatar" : "Avatar"))
|
||||||
|
|
||||||
if let id = account?.id,
|
if let id = account?.id,
|
||||||
let account = mastodonController.persistentContainer.account(for: id) {
|
let account = mastodonController.persistentContainer.account(for: id) {
|
||||||
|
|
|
@ -43,10 +43,10 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
|
||||||
super.init(rootView: wrapper)
|
super.init(rootView: wrapper)
|
||||||
|
|
||||||
self.uiState.delegate = self
|
self.uiState.delegate = self
|
||||||
pasteConfiguration = UIPasteConfiguration(forAccepting: CompositionAttachment.self)
|
|
||||||
userActivity = UserActivityManager.newPostActivity(accountID: mastodonController.accountInfo!.id)
|
|
||||||
|
|
||||||
updateNavigationTitle(draft: uiState.draft)
|
pasteConfiguration = UIPasteConfiguration(forAccepting: CompositionAttachment.self)
|
||||||
|
|
||||||
|
userActivity = UserActivityManager.newPostActivity(accountID: mastodonController.accountInfo!.id)
|
||||||
|
|
||||||
self.uiState.$draft
|
self.uiState.$draft
|
||||||
.flatMap(\.objectWillChange)
|
.flatMap(\.objectWillChange)
|
||||||
|
@ -55,27 +55,12 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
|
||||||
DraftsManager.save()
|
DraftsManager.save()
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
self.uiState.$draft
|
|
||||||
.sink { [unowned self] draft in
|
|
||||||
self.updateNavigationTitle(draft: draft)
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateNavigationTitle(draft: Draft) {
|
|
||||||
if let id = draft.inReplyToID,
|
|
||||||
let status = mastodonController.persistentContainer.status(for: id) {
|
|
||||||
navigationItem.title = "Reply to @\(status.account.acct)"
|
|
||||||
} else {
|
|
||||||
navigationItem.title = "New Post"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
@ -107,11 +92,6 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func accessibilityPerformEscape() -> Bool {
|
|
||||||
dismissCompose(mode: .cancel)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Duckable
|
// MARK: Duckable
|
||||||
|
|
||||||
func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {
|
func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {
|
||||||
|
|
|
@ -80,7 +80,6 @@ struct ComposeReplyView: View {
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
|
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
|
||||||
.offset(x: 0, y: offset)
|
.offset(x: 0, y: offset)
|
||||||
.accessibilityHidden(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@ struct ComposeView: View {
|
||||||
globalFrameOutsideList = frame
|
globalFrameOutsideList = frame
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.navigationTitle(navTitle)
|
||||||
.sheet(isPresented: $uiState.isShowingDraftsList) {
|
.sheet(isPresented: $uiState.isShowingDraftsList) {
|
||||||
DraftsView(currentDraft: draft, mastodonController: mastodonController)
|
DraftsView(currentDraft: draft, mastodonController: mastodonController)
|
||||||
}
|
}
|
||||||
|
@ -202,19 +203,23 @@ struct ComposeView: View {
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
ComposeCurrentAccount()
|
ComposeCurrentAccount()
|
||||||
.accessibilitySortPriority(1)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(verbatim: charactersRemaining.description)
|
Text(verbatim: charactersRemaining.description)
|
||||||
.foregroundColor(charactersRemaining < 0 ? .red : .secondary)
|
.foregroundColor(charactersRemaining < 0 ? .red : .secondary)
|
||||||
.font(Font.body.monospacedDigit())
|
.font(Font.body.monospacedDigit())
|
||||||
.accessibility(label: Text(charactersRemaining < 0 ? "\(-charactersRemaining) characters too many" : "\(charactersRemaining) characters remaining"))
|
.accessibility(label: Text(charactersRemaining < 0 ? "\(-charactersRemaining) characters too many" : "\(charactersRemaining) characters remaining"))
|
||||||
// this should come first, so VO users can back to it from the main compose text view
|
|
||||||
.accessibilitySortPriority(0)
|
|
||||||
}.frame(height: 50)
|
}.frame(height: 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var navTitle: Text {
|
||||||
|
if let id = draft.inReplyToID,
|
||||||
|
let status = mastodonController.persistentContainer.status(for: id) {
|
||||||
|
return Text("Reply to @\(status.account.acct)")
|
||||||
|
} else {
|
||||||
|
return Text("New Post")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var cancelButton: some View {
|
private var cancelButton: some View {
|
||||||
Button(action: self.cancel) {
|
Button(action: self.cancel) {
|
||||||
Text("Cancel")
|
Text("Cancel")
|
||||||
|
|
|
@ -12,21 +12,10 @@ import Duckable
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
extension DuckableContainerViewController: TuskerRootViewController {
|
extension DuckableContainerViewController: TuskerRootViewController {
|
||||||
func stateRestorationActivity() -> NSUserActivity? {
|
func stateRestorationActivity() -> NSUserActivity? {
|
||||||
var activity = (child as? TuskerRootViewController)?.stateRestorationActivity()
|
(child as? TuskerRootViewController)?.stateRestorationActivity()
|
||||||
if let compose = duckedViewController as? ComposeHostingController,
|
|
||||||
compose.draft.hasContent {
|
|
||||||
activity = UserActivityManager.addDuckedDraft(to: activity, draft: compose.draft)
|
|
||||||
}
|
|
||||||
return activity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreActivity(_ activity: NSUserActivity) {
|
func restoreActivity(_ activity: NSUserActivity) {
|
||||||
if let draft = UserActivityManager.getDraft(from: activity),
|
|
||||||
let account = UserActivityManager.getAccount(from: activity) {
|
|
||||||
let mastodonController = MastodonController.getForAccount(account)
|
|
||||||
let compose = ComposeHostingController(draft: draft, mastodonController: mastodonController)
|
|
||||||
_ = presentDuckable(compose, animated: false, isDucked: true)
|
|
||||||
}
|
|
||||||
(child as? TuskerRootViewController)?.restoreActivity(activity)
|
(child as? TuskerRootViewController)?.restoreActivity(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -391,7 +391,8 @@ extension MainSplitViewController: TuskerRootViewController {
|
||||||
return tabBarViewController.stateRestorationActivity()
|
return tabBarViewController.stateRestorationActivity()
|
||||||
} else {
|
} else {
|
||||||
if let timelinePages = navigationStackFor(item: .tab(.timelines))?.first as? TimelinesPageViewController {
|
if let timelinePages = navigationStackFor(item: .tab(.timelines))?.first as? TimelinesPageViewController {
|
||||||
return timelinePages.stateRestorationActivity()
|
let timeline = timelinePages.pageControllers[timelinePages.currentIndex] as! TimelineViewController
|
||||||
|
return timeline.stateRestorationActivity()
|
||||||
} else {
|
} else {
|
||||||
stateRestorationLogger.fault("MainSplitViewController: Unable to create state restoration activity")
|
stateRestorationLogger.fault("MainSplitViewController: Unable to create state restoration activity")
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -13,14 +13,11 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
private var composePlaceholder: UIViewController!
|
private var composePlaceholder: UIViewController!
|
||||||
|
|
||||||
private var fastAccountSwitcher: FastAccountSwitcherViewController!
|
private var fastAccountSwitcher: FastAccountSwitcherViewController!
|
||||||
|
|
||||||
private var fastSwitcherIndicator: FastAccountSwitcherIndicatorView!
|
private var fastSwitcherIndicator: FastAccountSwitcherIndicatorView!
|
||||||
private var fastSwitcherConstraints: [NSLayoutConstraint] = []
|
private var fastSwitcherConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
@available(iOS, obsoleted: 16.0)
|
|
||||||
private var draftToPresentOnAppear: Draft?
|
|
||||||
|
|
||||||
var selectedTab: Tab {
|
var selectedTab: Tab {
|
||||||
return Tab(rawValue: selectedIndex)!
|
return Tab(rawValue: selectedIndex)!
|
||||||
}
|
}
|
||||||
|
@ -88,11 +85,6 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
stateRestorationLogger.info("MainTabBarViewController: viewDidAppear, selectedIndex=\(self.selectedIndex, privacy: .public)")
|
stateRestorationLogger.info("MainTabBarViewController: viewDidAppear, selectedIndex=\(self.selectedIndex, privacy: .public)")
|
||||||
|
|
||||||
if let draftToPresentOnAppear {
|
|
||||||
self.draftToPresentOnAppear = nil
|
|
||||||
compose(editing: draftToPresentOnAppear, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
|
@ -243,39 +235,23 @@ extension MainTabBarViewController: TuskerNavigationDelegate {
|
||||||
extension MainTabBarViewController: TuskerRootViewController {
|
extension MainTabBarViewController: TuskerRootViewController {
|
||||||
func stateRestorationActivity() -> NSUserActivity? {
|
func stateRestorationActivity() -> NSUserActivity? {
|
||||||
let nav = viewController(for: .timelines) as! UINavigationController
|
let nav = viewController(for: .timelines) as! UINavigationController
|
||||||
var activity: NSUserActivity?
|
guard let timelinePages = nav.viewControllers.first as? TimelinesPageViewController,
|
||||||
if let timelinePages = nav.viewControllers.first as? TimelinesPageViewController {
|
let timelineVC = timelinePages.pageControllers[timelinePages.currentIndex] as? TimelineViewController else {
|
||||||
activity = timelinePages.stateRestorationActivity()
|
stateRestorationLogger.fault("MainTabBarViewController: Unable to create state restoration actiivty, couldn't find timeline/page VC")
|
||||||
} else {
|
return nil
|
||||||
stateRestorationLogger.fault("MainTabBarViewController: Unable to create state restoration activity, couldn't find timeline/page VC")
|
|
||||||
}
|
}
|
||||||
if let presentedNav = presentedViewController as? UINavigationController,
|
return timelineVC.stateRestorationActivity()
|
||||||
let compose = presentedNav.viewControllers.first as? ComposeHostingController {
|
|
||||||
activity = UserActivityManager.addEditedDraft(to: activity, draft: compose.draft)
|
|
||||||
}
|
|
||||||
return activity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreActivity(_ activity: NSUserActivity) {
|
func restoreActivity(_ activity: NSUserActivity) {
|
||||||
func restoreEditedDraft() {
|
|
||||||
// on iOS 16+, this is handled by the duckable container
|
|
||||||
if #unavailable(iOS 16.0),
|
|
||||||
let draft = UserActivityManager.getDraft(from: activity) {
|
|
||||||
draftToPresentOnAppear = draft
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if activity.activityType == UserActivityType.showTimeline.rawValue {
|
if activity.activityType == UserActivityType.showTimeline.rawValue {
|
||||||
let nav = viewController(for: .timelines) as! UINavigationController
|
let nav = viewController(for: .timelines) as! UINavigationController
|
||||||
guard let timelinePages = nav.viewControllers.first as? TimelinesPageViewController else {
|
guard let timelinePages = nav.viewControllers.first as? TimelinesPageViewController,
|
||||||
|
let timelineVC = timelinePages.pageControllers[timelinePages.currentIndex] as? TimelineViewController else {
|
||||||
stateRestorationLogger.fault("MainTabBarViewController: Unable to restore timeline activity, couldn't find VC")
|
stateRestorationLogger.fault("MainTabBarViewController: Unable to restore timeline activity, couldn't find VC")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
timelinePages.restoreActivity(activity)
|
timelineVC.restoreActivity(activity)
|
||||||
restoreEditedDraft()
|
|
||||||
} else if activity.activityType == UserActivityType.newPost.rawValue {
|
|
||||||
restoreEditedDraft()
|
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
stateRestorationLogger.fault("MainTabBarViewController: Unable to restore activity of unexpected type \(activity.activityType, privacy: .public)")
|
stateRestorationLogger.fault("MainTabBarViewController: Unable to restore activity of unexpected type \(activity.activityType, privacy: .public)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import CoreData
|
||||||
|
|
||||||
struct AdvancedPrefsView : View {
|
struct AdvancedPrefsView : View {
|
||||||
@ObservedObject var preferences = Preferences.shared
|
@ObservedObject var preferences = Preferences.shared
|
||||||
@State private var imageCacheSize: Int64 = 0
|
|
||||||
@State private var mastodonCacheSize: Int64 = 0
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
|
@ -66,42 +64,13 @@ struct AdvancedPrefsView : View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachingSection: some View {
|
var cachingSection: some View {
|
||||||
Section {
|
Section(header: Text("Caching"), footer: Text("Clearing caches will restart the app.")) {
|
||||||
Button(action: clearCache) {
|
Button(action: clearCache) {
|
||||||
Text("Clear Mastodon Cache")
|
Text("Clear Mastodon Cache")
|
||||||
}.foregroundColor(.red)
|
}.foregroundColor(.red)
|
||||||
Button(action: clearImageCaches) {
|
Button(action: clearImageCaches) {
|
||||||
Text("Clear Image Caches")
|
Text("Clear Image Caches")
|
||||||
}.foregroundColor(.red)
|
}.foregroundColor(.red)
|
||||||
} header: {
|
|
||||||
Text("Caching")
|
|
||||||
} footer: {
|
|
||||||
var s: AttributedString = "Clearing caches will restart the app."
|
|
||||||
if imageCacheSize != 0 {
|
|
||||||
s += AttributedString("\nImage cache size: \(ByteCountFormatter().string(fromByteCount: imageCacheSize))")
|
|
||||||
}
|
|
||||||
if mastodonCacheSize != 0 {
|
|
||||||
s += AttributedString("\nMastodon cache size: \(ByteCountFormatter().string(fromByteCount: mastodonCacheSize))")
|
|
||||||
}
|
|
||||||
return Text(s)
|
|
||||||
}.task {
|
|
||||||
imageCacheSize = [
|
|
||||||
ImageCache.avatars,
|
|
||||||
.headers,
|
|
||||||
.attachments,
|
|
||||||
.emojis,
|
|
||||||
].map {
|
|
||||||
$0.getDiskSizeInBytes() ?? 0
|
|
||||||
}.reduce(0, +)
|
|
||||||
mastodonCacheSize = LocalData.shared.accounts.map {
|
|
||||||
let descriptions = MastodonController.getForAccount($0).persistentContainer.persistentStoreDescriptions
|
|
||||||
return descriptions.map {
|
|
||||||
guard let url = $0.url else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return FileManager.default.recursiveSize(url: url) ?? 0
|
|
||||||
}.reduce(0, +)
|
|
||||||
}.reduce(0, +)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -252,10 +252,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doRestore() -> Bool {
|
private func doRestore() -> Bool {
|
||||||
guard let activity = activityToRestore else {
|
guard let activity = activityToRestore,
|
||||||
return false
|
let statusIDs = activity.userInfo?["statusIDs"] as? [String] else {
|
||||||
}
|
|
||||||
guard let statusIDs = activity.userInfo?["statusIDs"] as? [String] else {
|
|
||||||
stateRestorationLogger.fault("TimelineViewController: activity missing statusIDs")
|
stateRestorationLogger.fault("TimelineViewController: activity missing statusIDs")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,27 +46,21 @@ class TimelinesPageViewController: SegmentedPageViewController {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func stateRestorationActivity() -> NSUserActivity? {
|
|
||||||
return (pageControllers[currentIndex] as! TimelineViewController).stateRestorationActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
func restoreActivity(_ activity: NSUserActivity) {
|
func restoreActivity(_ activity: NSUserActivity) {
|
||||||
guard let timeline = UserActivityManager.getTimeline(from: activity) else {
|
guard let timeline = UserActivityManager.getTimeline(from: activity) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let index: Int
|
|
||||||
switch timeline {
|
switch timeline {
|
||||||
case .home:
|
case .home:
|
||||||
index = 0
|
selectPage(at: 0, animated: false)
|
||||||
case .public(local: false):
|
case .public(local: false):
|
||||||
index = 1
|
selectPage(at: 1, animated: false)
|
||||||
case .public(local: true):
|
case .public(local: true):
|
||||||
index = 2
|
selectPage(at: 2, animated: false)
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
selectPage(at: index, animated: false)
|
let timelineVC = pageControllers[currentIndex] as! TimelineViewController
|
||||||
let timelineVC = pageControllers[index] as! TimelineViewController
|
|
||||||
timelineVC.restoreActivity(activity)
|
timelineVC.restoreActivity(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
||||||
let titles: [String]
|
let titles: [String]
|
||||||
let pageControllers: [UIViewController]
|
let pageControllers: [UIViewController]
|
||||||
|
|
||||||
private var initialIndex = 0
|
|
||||||
private(set) var currentIndex = 0
|
private(set) var currentIndex = 0
|
||||||
|
|
||||||
var segmentedControl: UISegmentedControl!
|
var segmentedControl: UISegmentedControl!
|
||||||
|
@ -44,7 +43,7 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
||||||
|
|
||||||
view.backgroundColor = .systemBackground
|
view.backgroundColor = .systemBackground
|
||||||
|
|
||||||
selectPage(at: initialIndex, animated: false)
|
selectPage(at: 0, animated: false)
|
||||||
|
|
||||||
addKeyCommand(MenuController.prevSubTabCommand)
|
addKeyCommand(MenuController.prevSubTabCommand)
|
||||||
addKeyCommand(MenuController.nextSubTabCommand)
|
addKeyCommand(MenuController.nextSubTabCommand)
|
||||||
|
@ -58,10 +57,6 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectPage(at index: Int, animated: Bool) {
|
func selectPage(at index: Int, animated: Bool) {
|
||||||
guard isViewLoaded else {
|
|
||||||
initialIndex = index
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let direction: UIPageViewController.NavigationDirection = index - currentIndex > 0 ? .forward : .reverse
|
let direction: UIPageViewController.NavigationDirection = index - currentIndex > 0 ? .forward : .reverse
|
||||||
setViewControllers([pageControllers[index]], direction: direction, animated: animated)
|
setViewControllers([pageControllers[index]], direction: direction, animated: animated)
|
||||||
navigationItem.title = pageControllers[index].title
|
navigationItem.title = pageControllers[index].title
|
||||||
|
|
|
@ -91,37 +91,10 @@ class UserActivityManager {
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
|
||||||
static func addDuckedDraft(to activity: NSUserActivity?, draft: Draft) -> NSUserActivity {
|
|
||||||
if let activity {
|
|
||||||
activity.addUserInfoEntries(from: [
|
|
||||||
"duckedDraftID": draft.id.uuidString
|
|
||||||
])
|
|
||||||
return activity
|
|
||||||
} else {
|
|
||||||
return editDraftActivity(id: draft.id, accountID: draft.accountID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func addEditedDraft(to activity: NSUserActivity?, draft: Draft) -> NSUserActivity {
|
|
||||||
if let activity {
|
|
||||||
activity.addUserInfoEntries(from: [
|
|
||||||
"editedDraftID": draft.id.uuidString
|
|
||||||
])
|
|
||||||
return activity
|
|
||||||
} else {
|
|
||||||
return editDraftActivity(id: draft.id, accountID: draft.accountID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func getDraft(from activity: NSUserActivity) -> Draft? {
|
static func getDraft(from activity: NSUserActivity) -> Draft? {
|
||||||
let idStr: String?
|
guard activity.activityType == UserActivityType.newPost.rawValue,
|
||||||
if activity.activityType == UserActivityType.newPost.rawValue {
|
let str = activity.userInfo?["draftID"] as? String,
|
||||||
idStr = activity.userInfo?["draftID"] as? String
|
let uuid = UUID(uuidString: str) else {
|
||||||
} else {
|
|
||||||
idStr = activity.userInfo?["duckedDraftID"] as? String ?? activity.userInfo?["editedDraftID"] as? String
|
|
||||||
}
|
|
||||||
guard let idStr,
|
|
||||||
let uuid = UUID(uuidString: idStr) else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return DraftsManager.shared.getBy(id: uuid)
|
return DraftsManager.shared.getBy(id: uuid)
|
||||||
|
|
|
@ -88,7 +88,7 @@ extension TuskerNavigationDelegate {
|
||||||
show(conversation(mainStatusID: statusID, state: state), sender: self)
|
show(conversation(mainStatusID: statusID, state: state), sender: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compose(editing draft: Draft, animated: Bool = true) {
|
func compose(editing draft: Draft) {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
||||||
let options = UIWindowScene.ActivationRequestOptions()
|
let options = UIWindowScene.ActivationRequestOptions()
|
||||||
|
@ -97,20 +97,20 @@ extension TuskerNavigationDelegate {
|
||||||
} else {
|
} else {
|
||||||
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
||||||
if #available(iOS 16.0, *),
|
if #available(iOS 16.0, *),
|
||||||
presentDuckable(compose, animated: animated) {
|
presentDuckable(compose) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
||||||
let nav = UINavigationController(rootViewController: compose)
|
let nav = UINavigationController(rootViewController: compose)
|
||||||
nav.presentationController?.delegate = compose
|
nav.presentationController?.delegate = compose
|
||||||
present(nav, animated: animated)
|
present(nav, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compose(inReplyToID: String? = nil, mentioningAcct: String? = nil, animated: Bool = true) {
|
func compose(inReplyToID: String? = nil, mentioningAcct: String? = nil) {
|
||||||
let draft = apiController.createDraft(inReplyToID: inReplyToID, mentioningAcct: mentioningAcct)
|
let draft = apiController.createDraft(inReplyToID: inReplyToID, mentioningAcct: mentioningAcct)
|
||||||
compose(editing: draft, animated: animated)
|
compose(editing: draft)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController {
|
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController {
|
||||||
|
|
|
@ -14,7 +14,6 @@ import WebURL
|
||||||
import WebURLFoundationExtras
|
import WebURLFoundationExtras
|
||||||
|
|
||||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||||
private let dataDetectorsScheme = "x-apple-data-detectors"
|
|
||||||
|
|
||||||
class ContentTextView: LinkTextView, BaseEmojiLabel {
|
class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||||
|
|
||||||
|
@ -199,8 +198,7 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
let location = recognizer.location(in: self)
|
let location = recognizer.location(in: self)
|
||||||
if let (link, range) = getLinkAtPoint(location),
|
if let (link, range) = getLinkAtPoint(location) {
|
||||||
link.scheme != dataDetectorsScheme {
|
|
||||||
let text = (self.text as NSString).substring(with: range)
|
let text = (self.text as NSString).substring(with: range)
|
||||||
handleLinkTapped(url: link, text: text)
|
handleLinkTapped(url: link, text: text)
|
||||||
}
|
}
|
||||||
|
@ -289,15 +287,9 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||||
|
|
||||||
extension ContentTextView: UITextViewDelegate {
|
extension ContentTextView: UITextViewDelegate {
|
||||||
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||||
|
// generally disable the text view's link interactions, we handle tapping links ourself with a gesture recognizer
|
||||||
// the builtin data detectors use the x-apple-data-detectors scheme, and we allow the text view to handle those itself
|
// the builtin data detectors use the x-apple-data-detectors scheme, and we allow the text view to handle those itself
|
||||||
if URL.scheme == dataDetectorsScheme {
|
return URL.scheme == "x-apple-data-detectors"
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
// otherwise, regular taps are handled by the gesture recognizer, but the accessibility interaction to select links with the rotor goes through here
|
|
||||||
// and this seems to be the only way of overriding what it does
|
|
||||||
handleLinkTapped(url: URL, text: (text as NSString).substring(with: characterRange))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -395,27 +395,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
|
|
||||||
get {
|
|
||||||
guard let text = contentTextView.attributedText else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var actions: [UIAccessibilityCustomAction] = []
|
|
||||||
text.enumerateAttribute(.link, in: NSRange(location: 0, length: text.length)) { value, range, stop in
|
|
||||||
guard let value = value as? URL else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let text = text.attributedSubstring(from: range).string
|
|
||||||
actions.append(UIAccessibilityCustomAction(name: text) { [unowned self] _ in
|
|
||||||
self.contentTextView.handleLinkTapped(url: value, text: text)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return actions
|
|
||||||
}
|
|
||||||
set {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Configure UI
|
// MARK: Configure UI
|
||||||
|
|
||||||
func updateUI(statusID: String, state: StatusState) {
|
func updateUI(statusID: String, state: StatusState) {
|
||||||
|
|
|
@ -308,27 +308,6 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
|
|
||||||
get {
|
|
||||||
guard let text = contentTextView.attributedText else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var actions: [UIAccessibilityCustomAction] = []
|
|
||||||
text.enumerateAttribute(.link, in: NSRange(location: 0, length: text.length)) { value, range, stop in
|
|
||||||
guard let value = value as? URL else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let text = text.attributedSubstring(from: range).string
|
|
||||||
actions.append(UIAccessibilityCustomAction(name: text) { [unowned self] _ in
|
|
||||||
self.contentTextView.handleLinkTapped(url: value, text: text)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return actions
|
|
||||||
}
|
|
||||||
set {}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineStatusTableViewCell: SelectableTableViewCell {
|
extension TimelineStatusTableViewCell: SelectableTableViewCell {
|
||||||
|
|
Loading…
Reference in New Issue