mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
BugFix: Airdrop detection
This commit is contained in:
parent
98dfa7a7e5
commit
39d1135006
3 changed files with 89 additions and 33 deletions
|
|
@ -3135,7 +3135,7 @@ struct Shelf: View {
|
|||
}
|
||||
|
||||
init() {
|
||||
Task { await QuickShareService.shared.discoverAvailableProviders() }
|
||||
QuickShareService.shared.ensureDiscovered()
|
||||
}
|
||||
|
||||
private func highlightID(_ title: String) -> String {
|
||||
|
|
|
|||
|
|
@ -40,9 +40,19 @@ class QuickShareService: ObservableObject {
|
|||
// Hold security-scoped URLs during sharing
|
||||
private var sharingAccessingURLs: [URL] = []
|
||||
private var lifecycleDelegate: SharingLifecycleDelegate?
|
||||
private var hasDiscovered = false
|
||||
private var discoveryTask: Task<Void, Never>?
|
||||
|
||||
init() {
|
||||
Task {
|
||||
// Lazy discovery — don't block app launch.
|
||||
// Discovery runs on first access (shelf tab / settings Shelf section).
|
||||
}
|
||||
|
||||
/// Ensures providers are discovered exactly once. Safe to call multiple times.
|
||||
@MainActor
|
||||
func ensureDiscovered() {
|
||||
guard !hasDiscovered, discoveryTask == nil else { return }
|
||||
discoveryTask = Task {
|
||||
await discoverAvailableProviders()
|
||||
}
|
||||
}
|
||||
|
|
@ -51,41 +61,86 @@ class QuickShareService: ObservableObject {
|
|||
|
||||
@MainActor
|
||||
func discoverAvailableProviders() async {
|
||||
let finder = ShareServiceFinder()
|
||||
// Move the heavy NSSharingService enumeration off the main thread
|
||||
let result: (providers: [QuickShareProvider], services: [String: NSSharingService]) = await Task.detached(priority: .userInitiated) {
|
||||
let testItems: [Any] = [
|
||||
URL(string: "https://apple.com")! as NSURL,
|
||||
"Test Text" as NSString
|
||||
]
|
||||
|
||||
// Use simple test items without creating actual temp files
|
||||
// This avoids issues with the Share Sheet retaining references to deleted files
|
||||
let testItems: [Any] = [
|
||||
URL(string:"http://example.com") ?? URL(fileURLWithPath: "/"),
|
||||
"Test Text" as NSString
|
||||
]
|
||||
var nativeServices = NSSharingService.sharingServices(forItems: testItems)
|
||||
|
||||
let services = await finder.findApplicableServices(for: testItems)
|
||||
|
||||
var providers: [QuickShareProvider] = []
|
||||
|
||||
for svc in services {
|
||||
let title = svc.title
|
||||
let imgData = svc.image.tiffRepresentation
|
||||
let supportsRawText = svc.canPerform(withItems: ["Test Text"])
|
||||
let provider = QuickShareProvider(id: title, imageData: imgData, supportsRawText: supportsRawText)
|
||||
if !providers.contains(provider) {
|
||||
providers.append(provider)
|
||||
cachedServices[title] = svc
|
||||
// Manually inject essential system services that the static list may omit
|
||||
let manualServiceNames: [NSSharingService.Name] = [
|
||||
.composeEmail,
|
||||
.sendViaAirDrop,
|
||||
.composeMessage,
|
||||
.addToSafariReadingList
|
||||
]
|
||||
for name in manualServiceNames {
|
||||
if let service = NSSharingService(named: name),
|
||||
!nativeServices.contains(where: { $0.title == service.title }) {
|
||||
nativeServices.append(service)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let idx = providers.firstIndex(where: { $0.id == "AirDrop" }) {
|
||||
let ad = providers.remove(at: idx)
|
||||
providers.insert(ad, at: 0)
|
||||
}
|
||||
var providers: [QuickShareProvider] = []
|
||||
var services: [String: NSSharingService] = [:]
|
||||
|
||||
if !providers.contains(where: { $0.id == "System Share Menu" }) {
|
||||
providers.append(QuickShareProvider(id: "System Share Menu", imageData: nil, supportsRawText: true))
|
||||
}
|
||||
// Process each service inside an autoreleasepool so that
|
||||
// the large intermediate TIFF / bitmap buffers are freed
|
||||
// immediately instead of accumulating across the loop.
|
||||
for svc in nativeServices {
|
||||
let (provider, shouldCache) = autoreleasepool { () -> (QuickShareProvider, Bool) in
|
||||
let title = svc.title
|
||||
|
||||
self.availableProviders = providers
|
||||
// Downscale to a small thumbnail and store as compressed PNG
|
||||
// to keep total memory footprint minimal.
|
||||
let imgData: Data? = {
|
||||
let src = svc.image
|
||||
let thumbSize = NSSize(width: 32, height: 32)
|
||||
let thumb = NSImage(size: thumbSize)
|
||||
thumb.lockFocus()
|
||||
src.draw(in: NSRect(origin: .zero, size: thumbSize),
|
||||
from: NSRect(origin: .zero, size: src.size),
|
||||
operation: .copy, fraction: 1.0)
|
||||
thumb.unlockFocus()
|
||||
|
||||
guard let tiff = thumb.tiffRepresentation,
|
||||
let bitmap = NSBitmapImageRep(data: tiff) else { return nil }
|
||||
return bitmap.representation(using: .png, properties: [:])
|
||||
}()
|
||||
|
||||
let supportsRawText = svc.canPerform(withItems: ["Test Text"])
|
||||
let prov = QuickShareProvider(id: title, imageData: imgData, supportsRawText: supportsRawText)
|
||||
let isNew = !providers.contains(prov)
|
||||
return (prov, isNew)
|
||||
}
|
||||
|
||||
if shouldCache {
|
||||
providers.append(provider)
|
||||
services[provider.id] = svc
|
||||
}
|
||||
}
|
||||
|
||||
// Move AirDrop to the top
|
||||
if let idx = providers.firstIndex(where: { $0.id == "AirDrop" }) {
|
||||
let ad = providers.remove(at: idx)
|
||||
providers.insert(ad, at: 0)
|
||||
}
|
||||
|
||||
// System Share Menu fallback
|
||||
if !providers.contains(where: { $0.id == "System Share Menu" }) {
|
||||
providers.append(QuickShareProvider(id: "System Share Menu", imageData: nil, supportsRawText: true))
|
||||
}
|
||||
|
||||
return (providers, services)
|
||||
}.value
|
||||
|
||||
self.cachedServices = result.services
|
||||
self.availableProviders = result.providers
|
||||
self.hasDiscovered = true
|
||||
self.discoveryTask = nil
|
||||
}
|
||||
|
||||
// MARK: - File Picker
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ struct FileShareView: View {
|
|||
var body: some View {
|
||||
dropArea
|
||||
.background(NSViewHost(view: $hostView))
|
||||
.onAppear { quickShare.ensureDiscovered() }
|
||||
.onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data, .image], isTargeted: $vm.dropZoneTargeting) { providers in
|
||||
interactionNonce = .init()
|
||||
vm.dropEvent = true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue