Compare commits

..

5 Commits

12 changed files with 88 additions and 52 deletions

View File

@ -167,11 +167,23 @@ extension DraftAttachment: NSItemProviderReading {
type = .png 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) let attachment = DraftAttachment(entity: DraftsPersistentContainer.shared.persistentStoreCoordinator.managedObjectModel.entitiesByName["DraftAttachment"]!, insertInto: nil)
attachment.id = UUID() attachment.id = UUID()
attachment.fileURL = try writeDataToFile(data, id: attachment.id, type: type) attachment.fileURL = try writeDataToFile(data, id: attachment.id, type: type)
attachment.fileType = type.identifier attachment.fileType = type.identifier
attachment.attachmentDescription = "" attachment.attachmentDescription = caption
return attachment return attachment
} }

View File

@ -165,7 +165,6 @@
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; }; D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; }; D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; };
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA427A8D0020052936B /* WebURLFoundationExtras */; }; 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 */; }; D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; }; D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; };
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D674A50827F9128D00BA03AC /* Pachyderm */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tusker-Bridging-Header.h"; sourceTree = "<group>"; };
@ -1324,7 +1322,6 @@
D667E5F62135C2ED0057A976 /* Extensions */ = { D667E5F62135C2ED0057A976 /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */,
D663626B21361C6700C9CBA2 /* Account+Preferences.swift */, D663626B21361C6700C9CBA2 /* Account+Preferences.swift */,
D6F4D79329ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift */, D6F4D79329ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift */,
D6333B362137838300CE884A /* AttributedString+Helpers.swift */, D6333B362137838300CE884A /* AttributedString+Helpers.swift */,
@ -2216,7 +2213,6 @@
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */, D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */, D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */,
D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */, D646DCD22A06F2510059ECEB /* NotificationsCollectionViewController.swift in Sources */,
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameView.swift in Sources */, D6B4A4FF2506B81A000C81C1 /* AccountDisplayNameView.swift in Sources */,
D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */, D63D8DF42850FE7A008D95E1 /* ViewTags.swift in Sources */,
D630C3CC2BC5FD4600208903 /* GetAuthorizationTokenService.swift in Sources */, D630C3CC2BC5FD4600208903 /* GetAuthorizationTokenService.swift in Sources */,

View File

@ -19,7 +19,7 @@ import UserAccounts
fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentStore") fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentStore")
class MastodonCachePersistentStore: NSPersistentCloudKitContainer { class MastodonCachePersistentStore: NSPersistentCloudKitContainer, @unchecked Sendable {
private let accountInfo: UserAccountInfo? private let accountInfo: UserAccountInfo?

View File

@ -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
}
}

View File

@ -88,7 +88,7 @@ struct AnnouncementListRow: View {
Button(role: .destructive) { Button(role: .destructive) {
Task { Task {
await dismissAnnouncement() await dismissAnnouncement()
await removeAnnouncement() removeAnnouncement()
} }
} label: { } label: {
Label("Dismiss", systemImage: "xmark") Label("Dismiss", systemImage: "xmark")

View File

@ -89,12 +89,14 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
hideControlsWorkItem?.cancel() hideControlsWorkItem?.cancel()
if player.rate > 0 && info.oldValue == 0 { if player.rate > 0 && info.oldValue == 0 {
hideControlsWorkItem = DispatchWorkItem { [weak self] in hideControlsWorkItem = DispatchWorkItem { [weak self] in
guard let self, MainActor.runUnsafely {
let container = self.container, guard let self,
container.galleryControlsVisible else { let container = self.container,
return container.galleryControlsVisible else {
return
}
container.setGalleryControlsVisible(false, animated: true)
} }
container.setGalleryControlsVisible(false, animated: true)
} }
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: hideControlsWorkItem!) DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: hideControlsWorkItem!)
} }
@ -114,12 +116,45 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon
MainActor.runUnsafely { MainActor.runUnsafely {
if item.status == .readyToPlay { if item.status == .readyToPlay {
self.container?.setGalleryContentLoading(false) 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() { @objc private func preferencesChanged() {
if isGrayscale != Preferences.shared.grayscaleImages { if isGrayscale != Preferences.shared.grayscaleImages {
let isPlaying = player.rate > 0 let isPlaying = player.rate > 0

View File

@ -15,7 +15,7 @@ protocol MainSidebarViewControllerDelegate: AnyObject {
func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController) func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController)
func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item, previousItem: MainSidebarViewController.Item?) 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, 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 { class MainSidebarViewController: UIViewController {
@ -452,7 +452,7 @@ extension MainSidebarViewController: UICollectionViewDelegate {
} }
itemLastSelectedTimestamps[item] = Date() itemLastSelectedTimestamps[item] = Date()
if previouslySelectedItem == item { if previouslySelectedItem == item {
sidebarDelegate?.sidebar(self, scrollToTopFor: item) sidebarDelegate?.sidebar(self, didReselectItem: item)
} else if [MainSidebarViewController.Item.tab(.compose), .addList, .addSavedHashtag, .addSavedInstance].contains(item) { } else if [MainSidebarViewController.Item.tab(.compose), .addList, .addSavedHashtag, .addSavedInstance].contains(item) {
if let previous = previouslySelectedItem, let indexPath = dataSource.indexPath(for: previous) { if let previous = previouslySelectedItem, let indexPath = dataSource.indexPath(for: previous) {
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically) collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)

View File

@ -493,8 +493,12 @@ extension MainSplitViewController: MainSidebarViewControllerDelegate {
secondaryNavController.viewControllers = [viewController] secondaryNavController.viewControllers = [viewController]
} }
func sidebar(_ sidebarViewController: MainSidebarViewController, scrollToTopFor item: MainSidebarViewController.Item) { func sidebar(_ sidebarViewController: MainSidebarViewController, didReselectItem item: MainSidebarViewController.Item) {
(secondaryNavController as? TabBarScrollableViewController)?.tabBarScrollToTop() if secondaryNavController.viewControllers.count == 1 {
(secondaryNavController.topViewController as? TabBarScrollableViewController)?.tabBarScrollToTop()
} else {
secondaryNavController.popToRootViewController(animated: true)
}
} }
} }

View File

@ -441,10 +441,12 @@ extension NewMainTabBarViewController: UITabBarControllerDelegate {
return true return true
} else if let selectedTab, } else if let selectedTab,
selectedTab == tab, selectedTab == tab,
let nav = selectedViewController as? any NavigationControllerProtocol, let nav = selectedViewController as? any NavigationControllerProtocol {
nav.viewControllers.count == 1, if nav.viewControllers.count == 1 {
let scrollableVC = nav.viewControllers[0] as? TabBarScrollableViewController { (nav.viewControllers[0] as? TabBarScrollableViewController)?.tabBarScrollToTop()
scrollableVC.tabBarScrollToTop() } else {
nav.popToRootViewController(animated: true)
}
return false return false
} else { } else {
return true return true

View File

@ -87,7 +87,7 @@ struct PushInstanceSettingsView: View {
} }
let subscription = try await PushManager.shared.createSubscription(account: account) let subscription = try await PushManager.shared.createSubscription(account: account)
let mastodonController = await MastodonController.getForAccount(account) let mastodonController = MastodonController.getForAccount(account)
do { do {
let result = try await mastodonController.createPushSubscription(subscription: subscription) 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)") 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 return true
} catch { } catch {
// if creation failed, remove the subscription locally as well // if creation failed, remove the subscription locally as well
await PushManager.shared.removeSubscription(account: account) PushManager.shared.removeSubscription(account: account)
throw error throw error
} }
} }
private func disableNotifications() async throws { private func disableNotifications() async throws {
let mastodonController = await MastodonController.getForAccount(account) let mastodonController = MastodonController.getForAccount(account)
try await mastodonController.deletePushSubscription() try await mastodonController.deletePushSubscription()
await PushManager.shared.removeSubscription(account: account) PushManager.shared.removeSubscription(account: account)
subscription = nil subscription = nil
PushManager.logger.debug("Push subscription removed on \(account.instanceURL)") PushManager.logger.debug("Push subscription removed on \(account.instanceURL)")
} }
private func updateSubscription(alerts: PushNotifications.PushSubscription.Alerts, policy: PushNotifications.PushSubscription.Policy) async -> Bool { 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 { do {
let result = try await mastodonController.updatePushSubscription(alerts: alerts, policy: policy) let result = try await mastodonController.updatePushSubscription(alerts: alerts, policy: policy)
PushManager.logger.debug("Push subscription \(result.id, privacy: .public) updated on \(account.instanceURL)") 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?.alerts = alerts
subscription?.policy = policy subscription?.policy = policy
return true return true

View File

@ -241,13 +241,19 @@ class SplitNavigationController: UIViewController {
// otherwise the secondary nav's contents disappear immediately, rather than sliding off-screen // otherwise the secondary nav's contents disappear immediately, rather than sliding off-screen
let animator = UIViewPropertyAnimator(duration: 0.35, curve: .easeInOut) { let animator = UIViewPropertyAnimator(duration: 0.35, curve: .easeInOut) {
self.isLayingOutForAnimation = true 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() self.view.layoutIfNeeded()
} }
animator.addCompletion { _ in animator.addCompletion { _ in
self.secondaryNav.viewControllers = []
self.isLayingOutForAnimation = false self.isLayingOutForAnimation = false
// self.updateSecondaryNavVisibility() self.secondaryNav.viewControllers = []
self.updateSecondaryNavVisibility()
} }
animator.startAnimation() animator.startAnimation()
} else { } else {

View File

@ -61,7 +61,9 @@ class GifvController {
private func updatePresentationSizeObservation() { private func updatePresentationSizeObservation() {
presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] item, _ in presentationSizeObservation = item.observe(\.presentationSize, changeHandler: { [unowned self] item, _ in
self.presentationSizeSubject.send(item.presentationSize) DispatchQueue.main.async {
self.presentationSizeSubject.send(item.presentationSize)
}
}) })
} }