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() {
|
init() {
|
||||||
Task { await QuickShareService.shared.discoverAvailableProviders() }
|
QuickShareService.shared.ensureDiscovered()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func highlightID(_ title: String) -> String {
|
private func highlightID(_ title: String) -> String {
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,19 @@ class QuickShareService: ObservableObject {
|
||||||
// Hold security-scoped URLs during sharing
|
// Hold security-scoped URLs during sharing
|
||||||
private var sharingAccessingURLs: [URL] = []
|
private var sharingAccessingURLs: [URL] = []
|
||||||
private var lifecycleDelegate: SharingLifecycleDelegate?
|
private var lifecycleDelegate: SharingLifecycleDelegate?
|
||||||
|
private var hasDiscovered = false
|
||||||
|
private var discoveryTask: Task<Void, Never>?
|
||||||
|
|
||||||
init() {
|
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()
|
await discoverAvailableProviders()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -51,41 +61,86 @@ class QuickShareService: ObservableObject {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func discoverAvailableProviders() async {
|
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
|
var nativeServices = NSSharingService.sharingServices(forItems: testItems)
|
||||||
// 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
|
|
||||||
]
|
|
||||||
|
|
||||||
let services = await finder.findApplicableServices(for: testItems)
|
// Manually inject essential system services that the static list may omit
|
||||||
|
let manualServiceNames: [NSSharingService.Name] = [
|
||||||
var providers: [QuickShareProvider] = []
|
.composeEmail,
|
||||||
|
.sendViaAirDrop,
|
||||||
for svc in services {
|
.composeMessage,
|
||||||
let title = svc.title
|
.addToSafariReadingList
|
||||||
let imgData = svc.image.tiffRepresentation
|
]
|
||||||
let supportsRawText = svc.canPerform(withItems: ["Test Text"])
|
for name in manualServiceNames {
|
||||||
let provider = QuickShareProvider(id: title, imageData: imgData, supportsRawText: supportsRawText)
|
if let service = NSSharingService(named: name),
|
||||||
if !providers.contains(provider) {
|
!nativeServices.contains(where: { $0.title == service.title }) {
|
||||||
providers.append(provider)
|
nativeServices.append(service)
|
||||||
cachedServices[title] = svc
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let idx = providers.firstIndex(where: { $0.id == "AirDrop" }) {
|
|
||||||
let ad = providers.remove(at: idx)
|
|
||||||
providers.insert(ad, at: 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !providers.contains(where: { $0.id == "System Share Menu" }) {
|
var providers: [QuickShareProvider] = []
|
||||||
providers.append(QuickShareProvider(id: "System Share Menu", imageData: nil, supportsRawText: true))
|
var services: [String: NSSharingService] = [:]
|
||||||
}
|
|
||||||
|
|
||||||
self.availableProviders = providers
|
// 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
|
||||||
|
|
||||||
|
// 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
|
// MARK: - File Picker
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ struct FileShareView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
dropArea
|
dropArea
|
||||||
.background(NSViewHost(view: $hostView))
|
.background(NSViewHost(view: $hostView))
|
||||||
|
.onAppear { quickShare.ensureDiscovered() }
|
||||||
.onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data, .image], isTargeted: $vm.dropZoneTargeting) { providers in
|
.onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data, .image], isTargeted: $vm.dropZoneTargeting) { providers in
|
||||||
interactionNonce = .init()
|
interactionNonce = .init()
|
||||||
vm.dropEvent = true
|
vm.dropEvent = true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue