Compare commits
5 Commits
805e5eddd0
...
b663335c6d
Author | SHA1 | Date |
---|---|---|
Shadowfacts | b663335c6d | |
Shadowfacts | 9ce6bd566f | |
Shadowfacts | 9547bd2913 | |
Shadowfacts | 9b2e6140a3 | |
Shadowfacts | 6de255681c |
|
@ -167,11 +167,23 @@ extension DraftAttachment: NSItemProviderReading {
|
|||
type = .png
|
||||
}
|
||||
|
||||
// Read the caption from the image itself, if there is one.
|
||||
let caption: String
|
||||
if let source = CGImageSourceCreateWithData(data as CFData, [kCGImageSourceTypeIdentifierHint: typeIdentifier as CFString] as CFDictionary),
|
||||
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any],
|
||||
// This is the dictionary for TIFF properties, but it's present for other image types too
|
||||
let tiffProperties = properties[kCGImagePropertyTIFFDictionary as String] as? [String: Any],
|
||||
let imageDescription = tiffProperties[kCGImagePropertyTIFFImageDescription as String] as? String {
|
||||
caption = imageDescription
|
||||
} else {
|
||||
caption = ""
|
||||
}
|
||||
|
||||
let attachment = DraftAttachment(entity: DraftsPersistentContainer.shared.persistentStoreCoordinator.managedObjectModel.entitiesByName["DraftAttachment"]!, insertInto: nil)
|
||||
attachment.id = UUID()
|
||||
attachment.fileURL = try writeDataToFile(data, id: attachment.id, type: type)
|
||||
attachment.fileType = type.identifier
|
||||
attachment.attachmentDescription = ""
|
||||
attachment.attachmentDescription = caption
|
||||
return attachment
|
||||
}
|
||||
|
||||
|
|
|
@ -165,7 +165,6 @@
|
|||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
|
||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; };
|
||||
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA427A8D0020052936B /* WebURLFoundationExtras */; };
|
||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */; };
|
||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
|
||||
D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; };
|
||||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D674A50827F9128D00BA03AC /* Pachyderm */; };
|
||||
|
@ -599,7 +598,6 @@
|
|||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusStateResolver.swift; sourceTree = "<group>"; };
|
||||
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Preferences.swift"; sourceTree = "<group>"; };
|
||||
D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = "<group>"; };
|
||||
D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Equatable.swift"; sourceTree = "<group>"; };
|
||||
D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Traits.swift"; sourceTree = "<group>"; };
|
||||
D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tusker-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
|
@ -1324,7 +1322,6 @@
|
|||
D667E5F62135C2ED0057A976 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */,
|
||||
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */,
|
||||
D6F4D79329ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift */,
|
||||
D6333B362137838300CE884A /* AttributedString+Helpers.swift */,
|
||||
|
@ -2216,7 +2213,6 @@
|
|||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
|
||||
D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */,
|
||||
D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */,
|
||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameView.swift in Sources */,
|
||||
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
|
||||
D630C3CC2BC5FD4600208903 /* GetAuthorizationTokenService.swift in Sources */,
|
||||
|
|
|
@ -19,7 +19,7 @@ import UserAccounts
|
|||
|
||||
fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentStore")
|
||||
|
||||
class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
|
||||
class MastodonCachePersistentStore: NSPersistentCloudKitContainer, @unchecked Sendable {
|
||||
|
||||
private let accountInfo: UserAccountInfo?
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// Status+Equatable.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 8/28/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Pachyderm
|
||||
|
||||
extension Status: Equatable {
|
||||
public static func ==(lhs: Status, rhs: Status) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
extension Account: Equatable {
|
||||
public static func ==(lhs: Account, rhs: Account) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
|
@ -88,7 +88,7 @@ struct AnnouncementListRow: View {
|
|||
Button(role: .destructive) {
|
||||
Task {
|
||||
await dismissAnnouncement()
|
||||
await removeAnnouncement()
|
||||
removeAnnouncement()
|
||||
}
|
||||
} label: {
|
||||
Label("Dismiss", systemImage: "xmark")
|
||||
|
|
|
@ -89,6 +89,7 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
|||
hideControlsWorkItem?.cancel()
|
||||
if player.rate > 0 && info.oldValue == 0 {
|
||||
hideControlsWorkItem = DispatchWorkItem { [weak self] in
|
||||
MainActor.runUnsafely {
|
||||
guard let self,
|
||||
let container = self.container,
|
||||
container.galleryControlsVisible else {
|
||||
|
@ -96,6 +97,7 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
|||
}
|
||||
container.setGalleryControlsVisible(false, animated: true)
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: hideControlsWorkItem!)
|
||||
}
|
||||
})
|
||||
|
@ -114,12 +116,45 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
|
|||
MainActor.runUnsafely {
|
||||
if item.status == .readyToPlay {
|
||||
self.container?.setGalleryContentLoading(false)
|
||||
statusObservation = nil
|
||||
self.statusObservation = nil
|
||||
} else if item.status == .failed,
|
||||
let error = item.error {
|
||||
self.container?.setGalleryContentLoading(false)
|
||||
self.showErrorView(error)
|
||||
self.statusObservation = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func showErrorView(_ error: any Error) {
|
||||
let image = UIImageView(image: UIImage(systemName: "exclamationmark.triangle.fill")!)
|
||||
image.tintColor = .secondaryLabel
|
||||
image.contentMode = .scaleAspectFit
|
||||
|
||||
let label = UILabel()
|
||||
label.text = "Error Loading"
|
||||
label.font = .preferredFont(forTextStyle: .title1).withTraits(.traitBold)!
|
||||
label.textColor = .secondaryLabel
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [
|
||||
image,
|
||||
label,
|
||||
])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .center
|
||||
stackView.spacing = 8
|
||||
view.addSubview(stackView)
|
||||
NSLayoutConstraint.activate([
|
||||
image.widthAnchor.constraint(equalToConstant: 64),
|
||||
image.heightAnchor.constraint(equalToConstant: 64),
|
||||
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
@objc private func preferencesChanged() {
|
||||
if isGrayscale != Preferences.shared.grayscaleImages {
|
||||
let isPlaying = player.rate > 0
|
||||
|
|
|
@ -15,7 +15,7 @@ protocol MainSidebarViewControllerDelegate: AnyObject {
|
|||
func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController)
|
||||
func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item, previousItem: MainSidebarViewController.Item?)
|
||||
func sidebar(_ sidebarViewController: MainSidebarViewController, showViewController viewController: UIViewController, previousItem: MainSidebarViewController.Item?)
|
||||
func sidebar(_ sidebarViewController: MainSidebarViewController, scrollToTopFor item: MainSidebarViewController.Item)
|
||||
func sidebar(_ sidebarViewController: MainSidebarViewController, didReselectItem item: MainSidebarViewController.Item)
|
||||
}
|
||||
|
||||
class MainSidebarViewController: UIViewController {
|
||||
|
@ -452,7 +452,7 @@ extension MainSidebarViewController: UICollectionViewDelegate {
|
|||
}
|
||||
itemLastSelectedTimestamps[item] = Date()
|
||||
if previouslySelectedItem == item {
|
||||
sidebarDelegate?.sidebar(self, scrollToTopFor: item)
|
||||
sidebarDelegate?.sidebar(self, didReselectItem: item)
|
||||
} else if [MainSidebarViewController.Item.tab(.compose), .addList, .addSavedHashtag, .addSavedInstance].contains(item) {
|
||||
if let previous = previouslySelectedItem, let indexPath = dataSource.indexPath(for: previous) {
|
||||
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
|
||||
|
|
|
@ -493,8 +493,12 @@ extension MainSplitViewController: MainSidebarViewControllerDelegate {
|
|||
secondaryNavController.viewControllers = [viewController]
|
||||
}
|
||||
|
||||
func sidebar(_ sidebarViewController: MainSidebarViewController, scrollToTopFor item: MainSidebarViewController.Item) {
|
||||
(secondaryNavController as? TabBarScrollableViewController)?.tabBarScrollToTop()
|
||||
func sidebar(_ sidebarViewController: MainSidebarViewController, didReselectItem item: MainSidebarViewController.Item) {
|
||||
if secondaryNavController.viewControllers.count == 1 {
|
||||
(secondaryNavController.topViewController as? TabBarScrollableViewController)?.tabBarScrollToTop()
|
||||
} else {
|
||||
secondaryNavController.popToRootViewController(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -441,10 +441,12 @@ extension NewMainTabBarViewController: UITabBarControllerDelegate {
|
|||
return true
|
||||
} else if let selectedTab,
|
||||
selectedTab == tab,
|
||||
let nav = selectedViewController as? any NavigationControllerProtocol,
|
||||
nav.viewControllers.count == 1,
|
||||
let scrollableVC = nav.viewControllers[0] as? TabBarScrollableViewController {
|
||||
scrollableVC.tabBarScrollToTop()
|
||||
let nav = selectedViewController as? any NavigationControllerProtocol {
|
||||
if nav.viewControllers.count == 1 {
|
||||
(nav.viewControllers[0] as? TabBarScrollableViewController)?.tabBarScrollToTop()
|
||||
} else {
|
||||
nav.popToRootViewController(animated: true)
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
|
|
|
@ -87,7 +87,7 @@ struct PushInstanceSettingsView: View {
|
|||
}
|
||||
|
||||
let subscription = try await PushManager.shared.createSubscription(account: account)
|
||||
let mastodonController = await MastodonController.getForAccount(account)
|
||||
let mastodonController = MastodonController.getForAccount(account)
|
||||
do {
|
||||
let result = try await mastodonController.createPushSubscription(subscription: subscription)
|
||||
PushManager.logger.debug("Push subscription \(result.id, privacy: .public) created on \(account.instanceURL) with endpoint \(result.endpoint, privacy: .public)")
|
||||
|
@ -95,25 +95,25 @@ struct PushInstanceSettingsView: View {
|
|||
return true
|
||||
} catch {
|
||||
// if creation failed, remove the subscription locally as well
|
||||
await PushManager.shared.removeSubscription(account: account)
|
||||
PushManager.shared.removeSubscription(account: account)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private func disableNotifications() async throws {
|
||||
let mastodonController = await MastodonController.getForAccount(account)
|
||||
let mastodonController = MastodonController.getForAccount(account)
|
||||
try await mastodonController.deletePushSubscription()
|
||||
await PushManager.shared.removeSubscription(account: account)
|
||||
PushManager.shared.removeSubscription(account: account)
|
||||
subscription = nil
|
||||
PushManager.logger.debug("Push subscription removed on \(account.instanceURL)")
|
||||
}
|
||||
|
||||
private func updateSubscription(alerts: PushNotifications.PushSubscription.Alerts, policy: PushNotifications.PushSubscription.Policy) async -> Bool {
|
||||
let mastodonController = await MastodonController.getForAccount(account)
|
||||
let mastodonController = MastodonController.getForAccount(account)
|
||||
do {
|
||||
let result = try await mastodonController.updatePushSubscription(alerts: alerts, policy: policy)
|
||||
PushManager.logger.debug("Push subscription \(result.id, privacy: .public) updated on \(account.instanceURL)")
|
||||
await PushManager.shared.updateSubscription(account: account, alerts: alerts, policy: policy)
|
||||
PushManager.shared.updateSubscription(account: account, alerts: alerts, policy: policy)
|
||||
subscription?.alerts = alerts
|
||||
subscription?.policy = policy
|
||||
return true
|
||||
|
|
|
@ -241,13 +241,19 @@ class SplitNavigationController: UIViewController {
|
|||
// otherwise the secondary nav's contents disappear immediately, rather than sliding off-screen
|
||||
let animator = UIViewPropertyAnimator(duration: 0.35, curve: .easeInOut) {
|
||||
self.isLayingOutForAnimation = true
|
||||
self.setSecondaryVisible(false)
|
||||
NSLayoutConstraint.deactivate(self.constraints)
|
||||
self.constraints = [
|
||||
self.rootNav.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: self.rootNav.view.bounds.minX),
|
||||
self.rootNav.view.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
|
||||
self.secondaryNav.view.widthAnchor.constraint(equalToConstant: self.secondaryNav.view.bounds.width),
|
||||
]
|
||||
NSLayoutConstraint.activate(self.constraints)
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
animator.addCompletion { _ in
|
||||
self.secondaryNav.viewControllers = []
|
||||
self.isLayingOutForAnimation = false
|
||||
// self.updateSecondaryNavVisibility()
|
||||
self.secondaryNav.viewControllers = []
|
||||
self.updateSecondaryNavVisibility()
|
||||
}
|
||||
animator.startAnimation()
|
||||
} else {
|
||||
|
|
|
@ -61,7 +61,9 @@ class GifvController {
|
|||
|
||||
private func updatePresentationSizeObservation() {
|
||||
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] item, _ in
|
||||
DispatchQueue.main.async {
|
||||
self.presentationSizeSubject.send(item.presentationSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue