Compare commits
No commits in common. "8e570027a14967fa567c8247e9580919663829f8" and "361ce456cf95972a967e35ed1a3eaae44c0d26f1" have entirely different histories.
8e570027a1
...
361ce456cf
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,17 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2023.5 (96)
|
|
||||||
Features/Improvements:
|
|
||||||
- Resolve Mastodon's remote status links
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
- Fix handoff to iPad/Mac presenting new screen modally rather than navigating
|
|
||||||
- Fix crash if timeline gap cell is accessibility-activated after leaking
|
|
||||||
- Fix various crashes when multiple Compose/Drafts screens are opened
|
|
||||||
- Delete orphaned draft attachments
|
|
||||||
- Fix deleted posts not getting removed from Notifications screen
|
|
||||||
- Fix replied-to status not changing when selecting draft
|
|
||||||
|
|
||||||
## 2023.5 (94)
|
## 2023.5 (94)
|
||||||
Features/Improvements:
|
Features/Improvements:
|
||||||
- Apply filters to Notifications screen
|
- Apply filters to Notifications screen
|
||||||
|
|
|
@ -112,9 +112,6 @@ class AttachmentThumbnailController: ViewController {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case .none:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import Combine
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import TuskerComponents
|
import TuskerComponents
|
||||||
import MatchedGeometryPresentation
|
import MatchedGeometryPresentation
|
||||||
import CoreData
|
|
||||||
|
|
||||||
public final class ComposeController: ViewController {
|
public final class ComposeController: ViewController {
|
||||||
public typealias FetchAttachment = (URL) async -> UIImage?
|
public typealias FetchAttachment = (URL) async -> UIImage?
|
||||||
|
@ -55,9 +54,6 @@ public final class ComposeController: ViewController {
|
||||||
@Published public private(set) var didPostSuccessfully = false
|
@Published public private(set) var didPostSuccessfully = false
|
||||||
@Published var hasChangedLanguageSelection = false
|
@Published var hasChangedLanguageSelection = false
|
||||||
|
|
||||||
private var isDisappearing = false
|
|
||||||
private var userConfirmedDelete = false
|
|
||||||
|
|
||||||
var isPosting: Bool {
|
var isPosting: Bool {
|
||||||
poster != nil
|
poster != nil
|
||||||
}
|
}
|
||||||
|
@ -123,7 +119,6 @@ public final class ComposeController: ViewController {
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(currentInputModeChanged), name: UITextInputMode.currentInputModeDidChangeNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(currentInputModeChanged), name: UITextInputMode.currentInputModeDidChangeNotification, object: nil)
|
||||||
}
|
}
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: DraftsPersistentContainer.shared.viewContext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var view: some View {
|
public var view: some View {
|
||||||
|
@ -134,15 +129,6 @@ public final class ComposeController: ViewController {
|
||||||
.environment(\.composeUIConfig, config)
|
.environment(\.composeUIConfig, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
@objc private func managedObjectsDidChange(_ notification: Foundation.Notification) {
|
|
||||||
if let deleted = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject>,
|
|
||||||
deleted.contains(where: { $0.objectID == self.draft.objectID }),
|
|
||||||
!isDisappearing {
|
|
||||||
self.config.dismiss(.cancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func canPaste(itemProviders: [NSItemProvider]) -> Bool {
|
public func canPaste(itemProviders: [NSItemProvider]) -> Bool {
|
||||||
guard itemProviders.allSatisfy({ $0.canLoadObject(ofClass: DraftAttachment.self) }) else {
|
guard itemProviders.allSatisfy({ $0.canLoadObject(ofClass: DraftAttachment.self) }) else {
|
||||||
return false
|
return false
|
||||||
|
@ -186,7 +172,6 @@ public final class ComposeController: ViewController {
|
||||||
@MainActor
|
@MainActor
|
||||||
func cancel(deleteDraft: Bool) {
|
func cancel(deleteDraft: Bool) {
|
||||||
deleteDraftOnDisappear = true
|
deleteDraftOnDisappear = true
|
||||||
userConfirmedDelete = deleteDraft
|
|
||||||
config.dismiss(.cancel)
|
config.dismiss(.cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,18 +216,16 @@ public final class ComposeController: ViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectDraft(_ newDraft: Draft) {
|
func selectDraft(_ newDraft: Draft) {
|
||||||
let oldDraft = self.draft
|
if !self.draft.hasContent {
|
||||||
self.draft = newDraft
|
DraftsPersistentContainer.shared.viewContext.delete(self.draft)
|
||||||
|
|
||||||
if !oldDraft.hasContent {
|
|
||||||
DraftsPersistentContainer.shared.viewContext.delete(oldDraft)
|
|
||||||
}
|
}
|
||||||
DraftsPersistentContainer.shared.save()
|
DraftsPersistentContainer.shared.save()
|
||||||
|
|
||||||
|
self.draft = newDraft
|
||||||
}
|
}
|
||||||
|
|
||||||
func onDisappear() {
|
func onDisappear() {
|
||||||
isDisappearing = true
|
if deleteDraftOnDisappear && (!draft.hasContent || didPostSuccessfully) {
|
||||||
if deleteDraftOnDisappear && (!draft.hasContent || didPostSuccessfully || userConfirmedDelete) {
|
|
||||||
DraftsPersistentContainer.shared.viewContext.delete(draft)
|
DraftsPersistentContainer.shared.viewContext.delete(draft)
|
||||||
}
|
}
|
||||||
DraftsPersistentContainer.shared.save()
|
DraftsPersistentContainer.shared.save()
|
||||||
|
@ -369,8 +352,6 @@ public final class ComposeController: ViewController {
|
||||||
rowTopInset: 8,
|
rowTopInset: 8,
|
||||||
globalFrameOutsideList: globalFrameOutsideList
|
globalFrameOutsideList: globalFrameOutsideList
|
||||||
)
|
)
|
||||||
// i don't know why swiftui can't infer this from the status that's passed into the ReplyStatusView changing
|
|
||||||
.id(id)
|
|
||||||
.listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
|
.listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
.listRowBackground(config.backgroundColor)
|
.listRowBackground(config.backgroundColor)
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import TuskerComponents
|
import TuskerComponents
|
||||||
import CoreData
|
|
||||||
|
|
||||||
class DraftsController: ViewController {
|
class DraftsController: ViewController {
|
||||||
|
|
||||||
|
@ -153,11 +152,9 @@ private struct DraftRow: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if let lastModified = draft.lastModified {
|
Text(draft.lastModified.formatted(.abbreviatedTimeAgo))
|
||||||
Text(lastModified.formatted(.abbreviatedTimeAgo))
|
.font(.body)
|
||||||
.font(.body)
|
.foregroundColor(.secondary)
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ private struct DismissFocusedAttachmentButtonStyle: ButtonStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AttachmentDescriptionTextViewID: Hashable {
|
struct AttachmentDescriptionTextViewID: Hashable {
|
||||||
let attachmentID: UUID!
|
let attachmentID: UUID
|
||||||
|
|
||||||
init(_ attachment: DraftAttachment) {
|
init(_ attachment: DraftAttachment) {
|
||||||
self.attachmentID = attachment.id
|
self.attachmentID = attachment.id
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class Draft: NSManagedObject, Identifiable {
|
||||||
@NSManaged public var initialText: String
|
@NSManaged public var initialText: String
|
||||||
@NSManaged public var inReplyToID: String?
|
@NSManaged public var inReplyToID: String?
|
||||||
@NSManaged public var language: String? // ISO 639 language code
|
@NSManaged public var language: String? // ISO 639 language code
|
||||||
@NSManaged public var lastModified: Date!
|
@NSManaged public var lastModified: Date
|
||||||
@NSManaged public var localOnly: Bool
|
@NSManaged public var localOnly: Bool
|
||||||
@NSManaged public var text: String
|
@NSManaged public var text: String
|
||||||
@NSManaged private var visibilityStr: String
|
@NSManaged private var visibilityStr: String
|
||||||
|
|
|
@ -18,10 +18,6 @@ private let encoder = PropertyListEncoder()
|
||||||
@objc
|
@objc
|
||||||
public final class DraftAttachment: NSManagedObject, Identifiable {
|
public final class DraftAttachment: NSManagedObject, Identifiable {
|
||||||
|
|
||||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<DraftAttachment> {
|
|
||||||
return NSFetchRequest<DraftAttachment>(entityName: "DraftAttachment")
|
|
||||||
}
|
|
||||||
|
|
||||||
@NSManaged internal var assetID: String?
|
@NSManaged internal var assetID: String?
|
||||||
@NSManaged public var attachmentDescription: String
|
@NSManaged public var attachmentDescription: String
|
||||||
@NSManaged internal private(set) var drawingData: Data?
|
@NSManaged internal private(set) var drawingData: Data?
|
||||||
|
@ -30,7 +26,7 @@ public final class DraftAttachment: NSManagedObject, Identifiable {
|
||||||
@NSManaged public var editedAttachmentURL: URL?
|
@NSManaged public var editedAttachmentURL: URL?
|
||||||
@NSManaged public var fileURL: URL?
|
@NSManaged public var fileURL: URL?
|
||||||
@NSManaged internal var fileType: String?
|
@NSManaged internal var fileType: String?
|
||||||
@NSManaged public var id: UUID!
|
@NSManaged public var id: UUID
|
||||||
|
|
||||||
@NSManaged internal var draft: Draft
|
@NSManaged internal var draft: Draft
|
||||||
|
|
||||||
|
@ -58,7 +54,7 @@ public final class DraftAttachment: NSManagedObject, Identifiable {
|
||||||
} else if let fileURL, let fileType {
|
} else if let fileURL, let fileType {
|
||||||
return .file(fileURL, UTType(fileType)!)
|
return .file(fileURL, UTType(fileType)!)
|
||||||
} else {
|
} else {
|
||||||
return .none
|
fatalError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +72,6 @@ public final class DraftAttachment: NSManagedObject, Identifiable {
|
||||||
case drawing(PKDrawing)
|
case drawing(PKDrawing)
|
||||||
case file(URL, UTType)
|
case file(URL, UTType)
|
||||||
case editing(String, Attachment.Kind, URL)
|
case editing(String, Attachment.Kind, URL)
|
||||||
case none
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func prepareForDeletion() {
|
public override func prepareForDeletion() {
|
||||||
|
@ -159,13 +154,9 @@ extension DraftAttachment: NSItemProviderReading {
|
||||||
return attachment
|
return attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
static var attachmentsDirectory: URL {
|
|
||||||
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")!
|
|
||||||
return containerURL.appendingPathComponent("Documents").appendingPathComponent("attachments")
|
|
||||||
}
|
|
||||||
|
|
||||||
static func writeDataToFile(_ data: Data, id: UUID, type: UTType) throws -> URL {
|
static func writeDataToFile(_ data: Data, id: UUID, type: UTType) throws -> URL {
|
||||||
let directoryURL = attachmentsDirectory
|
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")!
|
||||||
|
let directoryURL = containerURL.appendingPathComponent("Documents").appendingPathComponent("attachments")
|
||||||
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true)
|
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true)
|
||||||
let attachmentURL = directoryURL.appendingPathComponent(id.uuidString, conformingTo: type)
|
let attachmentURL = directoryURL.appendingPathComponent(id.uuidString, conformingTo: type)
|
||||||
try data.write(to: attachmentURL)
|
try data.write(to: attachmentURL)
|
||||||
|
@ -247,8 +238,6 @@ extension DraftAttachment {
|
||||||
completion(.success((fileData, type)))
|
completion(.success((fileData, type)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
completion(.failure(.noData))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,6 +301,5 @@ extension DraftAttachment {
|
||||||
case noVideoExportSession
|
case noVideoExportSession
|
||||||
case loadingDrawing
|
case loadingDrawing
|
||||||
case loadingData
|
case loadingData
|
||||||
case noData
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,29 +157,6 @@ public class DraftsPersistentContainer: NSPersistentContainer {
|
||||||
draft.attachments.add(draftAttachment)
|
draft.attachments.add(draftAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeOrphanedAttachments(completion: @escaping () -> Void) {
|
|
||||||
guard let files = try? FileManager.default.contentsOfDirectory(at: DraftAttachment.attachmentsDirectory, includingPropertiesForKeys: nil),
|
|
||||||
!files.isEmpty else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
performBackgroundTask { context in
|
|
||||||
let allAttachmentsReq = DraftAttachment.fetchRequest()
|
|
||||||
allAttachmentsReq.predicate = NSPredicate(format: "fileURL != nil")
|
|
||||||
guard let allAttachments = try? context.fetch(allAttachmentsReq) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let orphaned = Set(files).subtracting(allAttachments.lazy.compactMap(\.fileURL))
|
|
||||||
for url in orphaned {
|
|
||||||
do {
|
|
||||||
try FileManager.default.removeItem(at: url)
|
|
||||||
} catch {
|
|
||||||
logger.error("Failed to remove orphaned attachment: \(String(describing: error), privacy: .public)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func remoteChanges(_ notification: Foundation.Notification) {
|
@objc private func remoteChanges(_ notification: Foundation.Notification) {
|
||||||
guard let newHistoryToken = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken else {
|
guard let newHistoryToken = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken else {
|
||||||
return
|
return
|
||||||
|
|
|
@ -18,6 +18,10 @@ struct PollOptionView: View {
|
||||||
self.remove = remove
|
self.remove = remove
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var optionIndex: Int {
|
||||||
|
poll.options.index(of: option)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, background: controller.parent.config.backgroundColor)
|
Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, background: controller.parent.config.backgroundColor)
|
||||||
|
@ -37,8 +41,7 @@ struct PollOptionView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var textField: some View {
|
private var textField: some View {
|
||||||
let index = poll.options.index(of: option)
|
let placeholder = "Option \(optionIndex + 1)"
|
||||||
let placeholder = index != NSNotFound ? "Option \(index + 1)" : ""
|
|
||||||
let maxLength = controller.parent.mastodonController.instanceFeatures.maxPollOptionChars
|
let maxLength = controller.parent.mastodonController.instanceFeatures.maxPollOptionChars
|
||||||
return EmojiTextField(text: $option.text, placeholder: placeholder, maxLength: maxLength)
|
return EmojiTextField(text: $option.text, placeholder: placeholder, maxLength: maxLength)
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,7 +303,6 @@
|
||||||
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
||||||
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
||||||
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */; };
|
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */; };
|
||||||
D6D79F592A13293200AB2315 /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F582A13293200AB2315 /* BackgroundManager.swift */; };
|
|
||||||
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
||||||
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
||||||
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
||||||
|
@ -705,7 +704,6 @@
|
||||||
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
||||||
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
||||||
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
||||||
D6D79F582A13293200AB2315 /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
|
|
||||||
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||||
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1478,7 +1476,6 @@
|
||||||
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */,
|
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */,
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||||
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
||||||
D6D79F582A13293200AB2315 /* BackgroundManager.swift */,
|
|
||||||
D61F75B6293C119700C0B37F /* Filterer.swift */,
|
D61F75B6293C119700C0B37F /* Filterer.swift */,
|
||||||
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
||||||
D61F75BA293C183100C0B37F /* HTMLConverter.swift */,
|
D61F75BA293C183100C0B37F /* HTMLConverter.swift */,
|
||||||
|
@ -2094,7 +2091,6 @@
|
||||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||||
D6D79F592A13293200AB2315 /* BackgroundManager.swift in Sources */,
|
|
||||||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */,
|
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */,
|
||||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
||||||
D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */,
|
D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */,
|
||||||
|
@ -2382,7 +2378,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
@ -2448,7 +2444,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||||
|
@ -2474,7 +2470,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
@ -2503,7 +2499,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
@ -2532,7 +2528,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
@ -2687,7 +2683,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
@ -2718,7 +2714,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
@ -2824,7 +2820,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||||
|
@ -2850,7 +2846,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 96;
|
CURRENT_PROJECT_VERSION = 94;
|
||||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||||
|
|
|
@ -76,8 +76,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackgroundManager.shared.registerHandlers()
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
//
|
|
||||||
// BackgroundManager.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 5/15/23.
|
|
||||||
// Copyright © 2023 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import BackgroundTasks
|
|
||||||
import OSLog
|
|
||||||
import ComposeUI
|
|
||||||
|
|
||||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "BackgroundManager")
|
|
||||||
|
|
||||||
struct BackgroundManager {
|
|
||||||
static let shared = BackgroundManager()
|
|
||||||
|
|
||||||
private init() {}
|
|
||||||
|
|
||||||
static let cleanupAttachmentsIdentifier = "\(Bundle.main.bundleIdentifier!).cleanup-attachments"
|
|
||||||
|
|
||||||
func scheduleTasks() {
|
|
||||||
BGTaskScheduler.shared.getPendingTaskRequests { requests in
|
|
||||||
if !requests.contains(where: { $0.identifier == Self.cleanupAttachmentsIdentifier }) {
|
|
||||||
let request = BGProcessingTaskRequest(identifier: Self.cleanupAttachmentsIdentifier)
|
|
||||||
do {
|
|
||||||
try BGTaskScheduler.shared.submit(request)
|
|
||||||
} catch {
|
|
||||||
logger.error("Failed to schedule cleanup attachments: \(String(describing: error), privacy: .public)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerHandlers() {
|
|
||||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.cleanupAttachmentsIdentifier, using: nil, launchHandler: handleCleanupAttachments(_:))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleCleanupAttachments(_ task: BGTask) {
|
|
||||||
DraftsPersistentContainer.shared.removeOrphanedAttachments() {
|
|
||||||
task.setTaskCompleted(success: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,10 +2,8 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<array>
|
<false/>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).cleanup-attachments</string>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
@ -35,8 +33,6 @@
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
|
||||||
<false/>
|
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.social-networking</string>
|
<string>public.app-category.social-networking</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
@ -145,7 +141,6 @@
|
||||||
</dict>
|
</dict>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>processing</string>
|
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|
|
@ -159,8 +159,6 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||||
|
|
||||||
try? context.save()
|
try? context.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
BackgroundManager.shared.scheduleTasks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
|
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
|
||||||
|
|
|
@ -11,13 +11,6 @@ import Pachyderm
|
||||||
import WebURL
|
import WebURL
|
||||||
import WebURLFoundationExtras
|
import WebURLFoundationExtras
|
||||||
|
|
||||||
private let mastodonRemoteStatusRegex = try! NSRegularExpression(pattern: "^/@.+@.+/\\d{18}")
|
|
||||||
private func isLikelyMastodonRemoteStatus(url: URL) -> Bool {
|
|
||||||
let path = url.path
|
|
||||||
let range = NSRange(location: 0, length: path.utf16.count)
|
|
||||||
return mastodonRemoteStatusRegex.numberOfMatches(in: path, range: range) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConversationViewController: UIViewController {
|
class ConversationViewController: UIViewController {
|
||||||
|
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
@ -217,24 +210,11 @@ class ConversationViewController: UIViewController {
|
||||||
indicator.startAnimating()
|
indicator.startAnimating()
|
||||||
state = .loading(indicator)
|
state = .loading(indicator)
|
||||||
|
|
||||||
let effectiveURL: String
|
let url = WebURL(url)!.serialized(excludingFragment: true)
|
||||||
class RedirectBlocker: NSObject, URLSessionTaskDelegate {
|
let request = Client.search(query: url, types: [.statuses], resolve: true)
|
||||||
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isLikelyMastodonRemoteStatus(url: url),
|
|
||||||
let (_, response) = try? await URLSession.shared.data(from: url, delegate: RedirectBlocker()),
|
|
||||||
let location = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "location") {
|
|
||||||
effectiveURL = location
|
|
||||||
} else {
|
|
||||||
effectiveURL = WebURL(url)!.serialized(excludingFragment: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true)
|
|
||||||
do {
|
do {
|
||||||
let (results, _) = try await mastodonController.run(request)
|
let (results, _) = try await mastodonController.run(request)
|
||||||
guard let status = results.statuses.first(where: { $0.url?.serialized() == effectiveURL }) else {
|
guard let status = results.statuses.first(where: { $0.url?.serialized() == url }) else {
|
||||||
throw UnableToResolveError()
|
throw UnableToResolveError()
|
||||||
}
|
}
|
||||||
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
|
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
|
||||||
|
|
|
@ -144,15 +144,7 @@ class ExpandThreadCollectionViewCell: UICollectionViewListCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||||
var config = UIBackgroundConfiguration.listGroupedCell().updated(for: state)
|
backgroundConfiguration = .appListPlainCell(for: state)
|
||||||
if state.isFocused {
|
|
||||||
// use default
|
|
||||||
} else if state.isHighlighted || state.isSelected {
|
|
||||||
config.backgroundColor = .appSelectedCellBackground
|
|
||||||
} else {
|
|
||||||
config.backgroundColor = .appSecondaryBackground
|
|
||||||
}
|
|
||||||
backgroundConfiguration = config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,14 +115,6 @@ class MainSplitViewController: UISplitViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func show(_ vc: UIViewController, sender: Any?) {
|
|
||||||
if traitCollection.horizontalSizeClass == .regular {
|
|
||||||
secondaryNavController.show(vc, sender: sender)
|
|
||||||
} else {
|
|
||||||
super.show(vc, sender: sender)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func handleSidebarCommandTimelines() {
|
@objc func handleSidebarCommandTimelines() {
|
||||||
sidebar.select(item: .tab(.timelines), animated: false)
|
sidebar.select(item: .tab(.timelines), animated: false)
|
||||||
select(item: .tab(.timelines))
|
select(item: .tab(.timelines))
|
||||||
|
|
|
@ -120,8 +120,6 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
||||||
filterer.filtersChanged = { [unowned self] actionsChanged in
|
filterer.filtersChanged = { [unowned self] actionsChanged in
|
||||||
self.reapplyFilters(actionsChanged: actionsChanged)
|
self.reapplyFilters(actionsChanged: actionsChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
@ -255,24 +253,6 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
||||||
dataSource.apply(snapshot)
|
dataSource.apply(snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
|
||||||
guard let userInfo = notification.userInfo,
|
|
||||||
let accountID = mastodonController.accountInfo?.id,
|
|
||||||
userInfo["accountID"] as? String == accountID,
|
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var snapshot = dataSource.snapshot()
|
|
||||||
let items = snapshot.itemIdentifiers(inSection: .notifications)
|
|
||||||
let toDelete = statusIDs.flatMap { id in
|
|
||||||
items.lazy.filter { $0.group?.notifications.first?.status?.id == id }
|
|
||||||
}
|
|
||||||
if !toDelete.isEmpty {
|
|
||||||
snapshot.deleteItems(toDelete)
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func dismissNotificationsInGroup(at indexPath: IndexPath) async {
|
private func dismissNotificationsInGroup(at indexPath: IndexPath) async {
|
||||||
guard case .group(let group, let collapseState, let filterState) = dataSource.itemIdentifier(for: indexPath) else {
|
guard case .group(let group, let collapseState, let filterState) = dataSource.itemIdentifier(for: indexPath) else {
|
||||||
return
|
return
|
||||||
|
|
|
@ -180,9 +180,6 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func performSearch(query: String?) {
|
func performSearch(query: String?) {
|
||||||
guard isViewLoaded else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let query = query, !query.isEmpty else {
|
guard let query = query, !query.isEmpty else {
|
||||||
self.dataSource.apply(NSDiffableDataSourceSnapshot<Section, Item>())
|
self.dataSource.apply(NSDiffableDataSourceSnapshot<Section, Item>())
|
||||||
return
|
return
|
||||||
|
|
|
@ -63,7 +63,7 @@ class StatusActionAccountListViewController: UIViewController {
|
||||||
self.actionType = actionType
|
self.actionType = actionType
|
||||||
self.statusID = statusID
|
self.statusID = statusID
|
||||||
self.statusState = statusState
|
self.statusState = statusState
|
||||||
self.accountIDs = accountIDs?.uniques()
|
self.accountIDs = accountIDs
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,12 +135,11 @@ class TimelineGapCollectionViewCell: UICollectionViewCell {
|
||||||
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
|
override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
|
||||||
get {
|
get {
|
||||||
[TimelineGapDirection.below, .above].map { dir in
|
[TimelineGapDirection.below, .above].map { dir in
|
||||||
UIAccessibilityCustomAction(name: "Load \(dir.accessibilityLabel)") { [weak self] _ in
|
UIAccessibilityCustomAction(name: "Load \(dir.accessibilityLabel)") { [unowned self] _ in
|
||||||
guard let self else { return false }
|
|
||||||
Task {
|
Task {
|
||||||
self.showsIndicator = true
|
showsIndicator = true
|
||||||
await self.fillGap?(dir)
|
await fillGap?(dir)
|
||||||
self.showsIndicator = false
|
showsIndicator = false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,11 +305,9 @@ private class SplitSecondaryNavigationController: EnhancedNavigationViewControll
|
||||||
}
|
}
|
||||||
|
|
||||||
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
||||||
if viewControllers.isEmpty {
|
|
||||||
configureSecondarySplitCloseButton(for: viewController)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.pushViewController(viewController, animated: animated)
|
super.pushViewController(viewController, animated: animated)
|
||||||
|
|
||||||
|
configureSecondarySplitCloseButton(for: viewControllers.first!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureSecondarySplitCloseButton(for viewController: UIViewController) {
|
private func configureSecondarySplitCloseButton(for viewController: UIViewController) {
|
||||||
|
|
|
@ -205,7 +205,6 @@ enum PopoverSource {
|
||||||
private let statusPathRegex = try! NSRegularExpression(
|
private let statusPathRegex = try! NSRegularExpression(
|
||||||
pattern:
|
pattern:
|
||||||
"(^/@[a-z0-9_]+/\\d{18})" // mastodon
|
"(^/@[a-z0-9_]+/\\d{18})" // mastodon
|
||||||
+ "|(^/@.+@.+/\\d{18})" // mastodon remote
|
|
||||||
+ "|(^/notice/[a-z0-9]{18})" // pleroma
|
+ "|(^/notice/[a-z0-9]{18})" // pleroma
|
||||||
+ "|(^/notes/[a-z0-9]{10})" // misskey
|
+ "|(^/notes/[a-z0-9]{10})" // misskey
|
||||||
+ "|(^/p/[a-z0-9_]+/\\d{18})" // pixelfed
|
+ "|(^/p/[a-z0-9_]+/\\d{18})" // pixelfed
|
||||||
|
|
Loading…
Reference in New Issue