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 {
|
||||
@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
|
||||
while let vc = cur {
|
||||
if let container = vc as? DuckableContainerViewController {
|
||||
container.presentDuckable(viewController, animated: animated, isDucked: isDucked, completion: nil)
|
||||
container.presentDuckable(viewController, animated: true, completion: nil)
|
||||
return true
|
||||
} else {
|
||||
cur = vc.parent
|
||||
|
|
|
@ -17,14 +17,6 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
|||
private var bottomConstraint: NSLayoutConstraint!
|
||||
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) {
|
||||
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 {
|
||||
if animated,
|
||||
case .ducked(_, placeholder: let placeholder) = state {
|
||||
|
@ -77,14 +69,9 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
|||
}
|
||||
return
|
||||
}
|
||||
if isDucked {
|
||||
state = .ducked(viewController, placeholder: createPlaceholderForDuckedViewController(viewController))
|
||||
configureChildForDuckedPlaceholder()
|
||||
} else {
|
||||
state = .presentingDucked(viewController, isFirstPresentation: true)
|
||||
doPresentDuckable(viewController, animated: animated, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func doPresentDuckable(_ viewController: DuckableViewController, animated: Bool, completion: (() -> Void)?) {
|
||||
viewController.duckableDelegate = self
|
||||
|
@ -92,7 +79,9 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
|||
nav.modalPresentationStyle = .custom
|
||||
nav.transitioningDelegate = self
|
||||
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?()
|
||||
}
|
||||
}
|
||||
|
@ -138,18 +127,10 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
|||
}
|
||||
let placeholder = createPlaceholderForDuckedViewController(viewController)
|
||||
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.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
child.view.layer.masksToBounds = true
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
@objc func unduckViewController() {
|
||||
|
@ -210,10 +191,7 @@ extension DuckableContainerViewController: UIViewControllerTransitioningDelegate
|
|||
@available(iOS 16.0, *)
|
||||
extension DuckableContainerViewController: UISheetPresentationControllerDelegate {
|
||||
public func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
|
||||
guard let snapshot = child.view.snapshotView(afterScreenUpdates: false) else {
|
||||
setOverrideTraitCollection(UITraitCollection(userInterfaceLevel: .elevated), forChild: child)
|
||||
return
|
||||
}
|
||||
let snapshot = child.view.snapshotView(afterScreenUpdates: false)!
|
||||
snapshot.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.view.addSubview(snapshot)
|
||||
NSLayoutConstraint.activate([
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
D61F758A2932E1FC00C0B37F /* SwipeActionsPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */; };
|
||||
D61F758D2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F758B2933C69C00C0B37F /* StatusUpdatedNotificationTableViewCell.swift */; };
|
||||
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 */; };
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -1168,7 +1166,6 @@
|
|||
D62E9984279CA23900C26176 /* URLSession+Development.swift */,
|
||||
D6ADB6ED28EA74E8009924AB /* UIView+Configure.swift */,
|
||||
D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */,
|
||||
D61F758F29353B4300C0B37F /* FileManager+Size.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1938,7 +1935,6 @@
|
|||
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */,
|
||||
D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */,
|
||||
D6DD2A45273D6C5700386A6C /* GIFImageView.swift in Sources */,
|
||||
D61F759029353B4300C0B37F /* FileManager+Size.swift in Sources */,
|
||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.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 {
|
||||
|
|
|
@ -110,10 +110,6 @@ class ImageCache {
|
|||
try cache.removeAll()
|
||||
}
|
||||
|
||||
func getDiskSizeInBytes() -> Int64? {
|
||||
return cache.disk?.getSizeInBytes()
|
||||
}
|
||||
|
||||
typealias Request = URLSessionDataTask
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import UIKit
|
|||
class ImageDataCache {
|
||||
|
||||
private let memory: MemoryCache<Entry>
|
||||
let disk: DiskCache<Data>?
|
||||
private let disk: DiskCache<Data>?
|
||||
|
||||
private let storeOriginalDataInMemory: Bool
|
||||
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 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()
|
||||
completion(true)
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ private func createReblogAction(status: StatusMO, container: StatusSwipeActionCo
|
|||
}
|
||||
let title = status.reblogged ? "Unreblog" : "Reblog"
|
||||
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()
|
||||
completion(true)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ struct ComposeCurrentAccount: View {
|
|||
ComposeAvatarImageView(url: account?.avatar)
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
|
||||
.accessibilityHidden(true)
|
||||
.accessibility(label: Text(account != nil ? "\(account!.displayName) avatar" : "Avatar"))
|
||||
|
||||
if let id = account?.id,
|
||||
let account = mastodonController.persistentContainer.account(for: id) {
|
||||
|
|
|
@ -43,10 +43,10 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
|
|||
super.init(rootView: wrapper)
|
||||
|
||||
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
|
||||
.flatMap(\.objectWillChange)
|
||||
|
@ -55,27 +55,12 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
|
|||
DraftsManager.save()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
self.uiState.$draft
|
||||
.sink { [unowned self] draft in
|
||||
self.updateNavigationTitle(draft: draft)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
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) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
|
@ -107,11 +92,6 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
|
|||
}
|
||||
}
|
||||
|
||||
override func accessibilityPerformEscape() -> Bool {
|
||||
dismissCompose(mode: .cancel)
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: Duckable
|
||||
|
||||
func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {
|
||||
|
|
|
@ -80,7 +80,6 @@ struct ComposeReplyView: View {
|
|||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
|
||||
.offset(x: 0, y: offset)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ struct ComposeView: View {
|
|||
globalFrameOutsideList = frame
|
||||
}
|
||||
})
|
||||
.navigationTitle(navTitle)
|
||||
.sheet(isPresented: $uiState.isShowingDraftsList) {
|
||||
DraftsView(currentDraft: draft, mastodonController: mastodonController)
|
||||
}
|
||||
|
@ -202,19 +203,23 @@ struct ComposeView: View {
|
|||
private var header: some View {
|
||||
HStack(alignment: .top) {
|
||||
ComposeCurrentAccount()
|
||||
.accessibilitySortPriority(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(verbatim: charactersRemaining.description)
|
||||
.foregroundColor(charactersRemaining < 0 ? .red : .secondary)
|
||||
.font(Font.body.monospacedDigit())
|
||||
.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)
|
||||
}
|
||||
|
||||
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 {
|
||||
Button(action: self.cancel) {
|
||||
Text("Cancel")
|
||||
|
|
|
@ -12,21 +12,10 @@ import Duckable
|
|||
@available(iOS 16.0, *)
|
||||
extension DuckableContainerViewController: TuskerRootViewController {
|
||||
func stateRestorationActivity() -> NSUserActivity? {
|
||||
var activity = (child as? TuskerRootViewController)?.stateRestorationActivity()
|
||||
if let compose = duckedViewController as? ComposeHostingController,
|
||||
compose.draft.hasContent {
|
||||
activity = UserActivityManager.addDuckedDraft(to: activity, draft: compose.draft)
|
||||
}
|
||||
return activity
|
||||
(child as? TuskerRootViewController)?.stateRestorationActivity()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -391,7 +391,8 @@ extension MainSplitViewController: TuskerRootViewController {
|
|||
return tabBarViewController.stateRestorationActivity()
|
||||
} else {
|
||||
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 {
|
||||
stateRestorationLogger.fault("MainSplitViewController: Unable to create state restoration activity")
|
||||
return nil
|
||||
|
|
|
@ -13,14 +13,11 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
|||
weak var mastodonController: MastodonController!
|
||||
|
||||
private var composePlaceholder: UIViewController!
|
||||
|
||||
private var fastAccountSwitcher: FastAccountSwitcherViewController!
|
||||
|
||||
private var fastSwitcherIndicator: FastAccountSwitcherIndicatorView!
|
||||
private var fastSwitcherConstraints: [NSLayoutConstraint] = []
|
||||
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
private var draftToPresentOnAppear: Draft?
|
||||
|
||||
var selectedTab: Tab {
|
||||
return Tab(rawValue: selectedIndex)!
|
||||
}
|
||||
|
@ -88,11 +85,6 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
|||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
stateRestorationLogger.info("MainTabBarViewController: viewDidAppear, selectedIndex=\(self.selectedIndex, privacy: .public)")
|
||||
|
||||
if let draftToPresentOnAppear {
|
||||
self.draftToPresentOnAppear = nil
|
||||
compose(editing: draftToPresentOnAppear, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
|
@ -243,39 +235,23 @@ extension MainTabBarViewController: TuskerNavigationDelegate {
|
|||
extension MainTabBarViewController: TuskerRootViewController {
|
||||
func stateRestorationActivity() -> NSUserActivity? {
|
||||
let nav = viewController(for: .timelines) as! UINavigationController
|
||||
var activity: NSUserActivity?
|
||||
if let timelinePages = nav.viewControllers.first as? TimelinesPageViewController {
|
||||
activity = timelinePages.stateRestorationActivity()
|
||||
} else {
|
||||
stateRestorationLogger.fault("MainTabBarViewController: Unable to create state restoration activity, couldn't find timeline/page VC")
|
||||
guard let timelinePages = nav.viewControllers.first as? TimelinesPageViewController,
|
||||
let timelineVC = timelinePages.pageControllers[timelinePages.currentIndex] as? TimelineViewController else {
|
||||
stateRestorationLogger.fault("MainTabBarViewController: Unable to create state restoration actiivty, couldn't find timeline/page VC")
|
||||
return nil
|
||||
}
|
||||
if let presentedNav = presentedViewController as? UINavigationController,
|
||||
let compose = presentedNav.viewControllers.first as? ComposeHostingController {
|
||||
activity = UserActivityManager.addEditedDraft(to: activity, draft: compose.draft)
|
||||
}
|
||||
return activity
|
||||
return timelineVC.stateRestorationActivity()
|
||||
}
|
||||
|
||||
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 {
|
||||
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")
|
||||
return
|
||||
}
|
||||
timelinePages.restoreActivity(activity)
|
||||
restoreEditedDraft()
|
||||
} else if activity.activityType == UserActivityType.newPost.rawValue {
|
||||
restoreEditedDraft()
|
||||
return
|
||||
timelineVC.restoreActivity(activity)
|
||||
} else {
|
||||
stateRestorationLogger.fault("MainTabBarViewController: Unable to restore activity of unexpected type \(activity.activityType, privacy: .public)")
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ import CoreData
|
|||
|
||||
struct AdvancedPrefsView : View {
|
||||
@ObservedObject var preferences = Preferences.shared
|
||||
@State private var imageCacheSize: Int64 = 0
|
||||
@State private var mastodonCacheSize: Int64 = 0
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
|
@ -66,42 +64,13 @@ struct AdvancedPrefsView : View {
|
|||
}
|
||||
|
||||
var cachingSection: some View {
|
||||
Section {
|
||||
Section(header: Text("Caching"), footer: Text("Clearing caches will restart the app.")) {
|
||||
Button(action: clearCache) {
|
||||
Text("Clear Mastodon Cache")
|
||||
}.foregroundColor(.red)
|
||||
Button(action: clearImageCaches) {
|
||||
Text("Clear Image Caches")
|
||||
}.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 {
|
||||
guard let activity = activityToRestore else {
|
||||
return false
|
||||
}
|
||||
guard let statusIDs = activity.userInfo?["statusIDs"] as? [String] else {
|
||||
guard let activity = activityToRestore,
|
||||
let statusIDs = activity.userInfo?["statusIDs"] as? [String] else {
|
||||
stateRestorationLogger.fault("TimelineViewController: activity missing statusIDs")
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -46,27 +46,21 @@ class TimelinesPageViewController: SegmentedPageViewController {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func stateRestorationActivity() -> NSUserActivity? {
|
||||
return (pageControllers[currentIndex] as! TimelineViewController).stateRestorationActivity()
|
||||
}
|
||||
|
||||
func restoreActivity(_ activity: NSUserActivity) {
|
||||
guard let timeline = UserActivityManager.getTimeline(from: activity) else {
|
||||
return
|
||||
}
|
||||
let index: Int
|
||||
switch timeline {
|
||||
case .home:
|
||||
index = 0
|
||||
selectPage(at: 0, animated: false)
|
||||
case .public(local: false):
|
||||
index = 1
|
||||
selectPage(at: 1, animated: false)
|
||||
case .public(local: true):
|
||||
index = 2
|
||||
selectPage(at: 2, animated: false)
|
||||
default:
|
||||
return
|
||||
}
|
||||
selectPage(at: index, animated: false)
|
||||
let timelineVC = pageControllers[index] as! TimelineViewController
|
||||
let timelineVC = pageControllers[currentIndex] as! TimelineViewController
|
||||
timelineVC.restoreActivity(activity)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
|||
let titles: [String]
|
||||
let pageControllers: [UIViewController]
|
||||
|
||||
private var initialIndex = 0
|
||||
private(set) var currentIndex = 0
|
||||
|
||||
var segmentedControl: UISegmentedControl!
|
||||
|
@ -44,7 +43,7 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
|||
|
||||
view.backgroundColor = .systemBackground
|
||||
|
||||
selectPage(at: initialIndex, animated: false)
|
||||
selectPage(at: 0, animated: false)
|
||||
|
||||
addKeyCommand(MenuController.prevSubTabCommand)
|
||||
addKeyCommand(MenuController.nextSubTabCommand)
|
||||
|
@ -58,10 +57,6 @@ class SegmentedPageViewController: UIPageViewController, UIPageViewControllerDel
|
|||
}
|
||||
|
||||
func selectPage(at index: Int, animated: Bool) {
|
||||
guard isViewLoaded else {
|
||||
initialIndex = index
|
||||
return
|
||||
}
|
||||
let direction: UIPageViewController.NavigationDirection = index - currentIndex > 0 ? .forward : .reverse
|
||||
setViewControllers([pageControllers[index]], direction: direction, animated: animated)
|
||||
navigationItem.title = pageControllers[index].title
|
||||
|
|
|
@ -91,37 +91,10 @@ class UserActivityManager {
|
|||
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? {
|
||||
let idStr: String?
|
||||
if activity.activityType == UserActivityType.newPost.rawValue {
|
||||
idStr = activity.userInfo?["draftID"] as? String
|
||||
} else {
|
||||
idStr = activity.userInfo?["duckedDraftID"] as? String ?? activity.userInfo?["editedDraftID"] as? String
|
||||
}
|
||||
guard let idStr,
|
||||
let uuid = UUID(uuidString: idStr) else {
|
||||
guard activity.activityType == UserActivityType.newPost.rawValue,
|
||||
let str = activity.userInfo?["draftID"] as? String,
|
||||
let uuid = UUID(uuidString: str) else {
|
||||
return nil
|
||||
}
|
||||
return DraftsManager.shared.getBy(id: uuid)
|
||||
|
|
|
@ -88,7 +88,7 @@ extension TuskerNavigationDelegate {
|
|||
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 {
|
||||
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
||||
let options = UIWindowScene.ActivationRequestOptions()
|
||||
|
@ -97,20 +97,20 @@ extension TuskerNavigationDelegate {
|
|||
} else {
|
||||
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
||||
if #available(iOS 16.0, *),
|
||||
presentDuckable(compose, animated: animated) {
|
||||
presentDuckable(compose) {
|
||||
return
|
||||
} else {
|
||||
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
||||
let nav = UINavigationController(rootViewController: 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)
|
||||
compose(editing: draft, animated: animated)
|
||||
compose(editing: draft)
|
||||
}
|
||||
|
||||
func loadingLargeImage(url: URL, cache: ImageCache, description: String?, animatingFrom sourceView: UIImageView) -> LoadingLargeImageViewController {
|
||||
|
|
|
@ -14,7 +14,6 @@ import WebURL
|
|||
import WebURLFoundationExtras
|
||||
|
||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||
private let dataDetectorsScheme = "x-apple-data-detectors"
|
||||
|
||||
class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||
|
||||
|
@ -199,8 +198,7 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
}
|
||||
|
||||
let location = recognizer.location(in: self)
|
||||
if let (link, range) = getLinkAtPoint(location),
|
||||
link.scheme != dataDetectorsScheme {
|
||||
if let (link, range) = getLinkAtPoint(location) {
|
||||
let text = (self.text as NSString).substring(with: range)
|
||||
handleLinkTapped(url: link, text: text)
|
||||
}
|
||||
|
@ -289,15 +287,9 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
|
||||
extension ContentTextView: UITextViewDelegate {
|
||||
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
|
||||
if URL.scheme == dataDetectorsScheme {
|
||||
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
|
||||
}
|
||||
return URL.scheme == "x-apple-data-detectors"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -395,27 +395,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
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
|
||||
|
||||
func updateUI(statusID: String, state: StatusState) {
|
||||
|
|
|
@ -308,27 +308,6 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
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 {
|
||||
|
|
Loading…
Reference in New Issue