fixed stale state after logout and replayed or lost share intents
This commit is contained in:
@@ -13,21 +13,44 @@ class SceneDelegate: FlutterSceneDelegate {
|
||||
options connectionOptions: UIScene.ConnectionOptions
|
||||
) {
|
||||
super.scene(scene, willConnectTo: session, options: connectionOptions)
|
||||
for context in connectionOptions.urlContexts {
|
||||
var handledShare = false
|
||||
for context in connectionOptions.urlContexts where isShareUrl(context.url) {
|
||||
_ = ReceiveSharingIntentPlugin.instance.application(
|
||||
UIApplication.shared,
|
||||
didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.url: context.url]
|
||||
)
|
||||
handledShare = true
|
||||
}
|
||||
if handledShare { clearSharedPayload() }
|
||||
}
|
||||
|
||||
override func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||
for context in URLContexts {
|
||||
let shares = URLContexts.filter { isShareUrl($0.url) }
|
||||
for context in shares {
|
||||
_ = ReceiveSharingIntentPlugin.instance.application(
|
||||
UIApplication.shared,
|
||||
open: context.url,
|
||||
options: [:]
|
||||
)
|
||||
}
|
||||
if !shares.isEmpty { clearSharedPayload() }
|
||||
let others = URLContexts.subtracting(shares)
|
||||
if !others.isEmpty { super.scene(scene, openURLContexts: others) }
|
||||
}
|
||||
|
||||
private func isShareUrl(_ url: URL) -> Bool {
|
||||
ReceiveSharingIntentPlugin.instance.hasMatchingSchemePrefix(url: url)
|
||||
}
|
||||
|
||||
// The plugin keeps the payload in memory once read but never removes it
|
||||
// from the App Group defaults. The Share Extension opens the app twice per
|
||||
// share and a scene reconnect replays the URL, so every later delivery
|
||||
// would publish the finished share again.
|
||||
private func clearSharedPayload() {
|
||||
let groupId = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String
|
||||
let defaults = UserDefaults(
|
||||
suiteName: groupId ?? "group.\(Bundle.main.bundleIdentifier ?? "")")
|
||||
defaults?.removeObject(forKey: "ShareKey")
|
||||
defaults?.removeObject(forKey: "ShareMessageKey")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,17 +152,54 @@ final class ShareViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func copyIntoAppGroup(src: URL) -> URL? {
|
||||
// loadItem-Callbacks laufen parallel; Ordneranlage und Namenswahl seriell.
|
||||
private let fileQueue = DispatchQueue(label: "share.files")
|
||||
|
||||
// Ein Ordner pro Share: Gleichnamige Dateien (z. B. bearbeitete Fotos, alle
|
||||
// "FullSizeRender.jpg") überschrieben sich sonst gegenseitig, auch über
|
||||
// zwei Shares hinweg. Die App löscht den Ordner, sobald der Share erledigt ist.
|
||||
private lazy var shareDir: URL? = {
|
||||
guard let container = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
|
||||
return nil
|
||||
}
|
||||
let name = src.lastPathComponent.isEmpty ? UUID().uuidString : src.lastPathComponent
|
||||
let dst = container.appendingPathComponent(name)
|
||||
let root = container.appendingPathComponent("share_intent", isDirectory: true)
|
||||
sweepStaleShares(in: root)
|
||||
let dir = root.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
return dir
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
// Shares, deren App-Flow nie abgeschlossen wurde (Prozess beendet o. ä.),
|
||||
// räumt sonst niemand weg — der App-Group-Container wird nie geleert.
|
||||
private func sweepStaleShares(in root: URL) {
|
||||
let fm = FileManager.default
|
||||
guard let entries = try? fm.contentsOfDirectory(
|
||||
at: root, includingPropertiesForKeys: [.creationDateKey]) else { return }
|
||||
let cutoff = Date().addingTimeInterval(-24 * 60 * 60)
|
||||
for entry in entries {
|
||||
let created = (try? entry.resourceValues(forKeys: [.creationDateKey]))?.creationDate
|
||||
if let created, created < cutoff { try? fm.removeItem(at: entry) }
|
||||
}
|
||||
}
|
||||
|
||||
private func copyIntoAppGroup(src: URL) -> URL? {
|
||||
fileQueue.sync { copyIntoShareDir(src: src) }
|
||||
}
|
||||
|
||||
private func copyIntoShareDir(src: URL) -> URL? {
|
||||
guard let dir = shareDir else { return nil }
|
||||
let name = src.lastPathComponent.isEmpty ? UUID().uuidString : src.lastPathComponent
|
||||
var dst = dir.appendingPathComponent(name)
|
||||
// Mehrfachauswahl mit gleichem Namen innerhalb eines Shares.
|
||||
if FileManager.default.fileExists(atPath: dst.path) {
|
||||
dst = dir.appendingPathComponent("\(UUID().uuidString.prefix(8))-\(name)")
|
||||
}
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: dst.path) {
|
||||
try FileManager.default.removeItem(at: dst)
|
||||
}
|
||||
try FileManager.default.copyItem(at: src, to: dst)
|
||||
return dst
|
||||
} catch {
|
||||
@@ -171,10 +208,9 @@ final class ShareViewController: UIViewController {
|
||||
}
|
||||
|
||||
private func writePng(image: UIImage) -> URL? {
|
||||
guard let container = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: appGroupId),
|
||||
let data = image.pngData() else { return nil }
|
||||
let dst = container.appendingPathComponent("\(UUID().uuidString).png")
|
||||
guard let data = image.pngData(),
|
||||
let dir = fileQueue.sync(execute: { shareDir }) else { return nil }
|
||||
let dst = dir.appendingPathComponent("\(UUID().uuidString).png")
|
||||
return (try? data.write(to: dst)) != nil ? dst : nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user