Improve iOS share extension implementation to skip interstitial UI and fix issues with subsequent image shares not updating the view. iOS Share Extension Improvements: - Replace SLComposeServiceViewController with custom UIViewController to eliminate interstitial "Post" button UI - Use minimal URL (timesafari://) instead of deep link for app launch - Implement app lifecycle detection via Capacitor appStateChange listener instead of relying solely on deep links Deep Link and Navigation Fixes: - Remove "shared-photo" from deep link schemas (no longer needed) - Add empty path URL handling for share extension launches - Implement processing lock to prevent duplicate image processing - Add retry mechanism (300ms delay) to handle race conditions with AppDelegate writing temp files - Use router.replace() when already on /shared-photo route to force refresh - Clear old images from temp DB before storing new ones - Delete temp file immediately after reading to prevent stale data SharedPhotoView Component: - Add route watcher (@Watch) to reload image when fileName query parameter changes - Extract image loading logic into reusable loadSharedImage() method - Improve error handling to clear image state on failures This fixes the issue where sharing a second image while already on SharedPhotoView would display the previous image instead of the new one.
157 lines
7.4 KiB
Swift
157 lines
7.4 KiB
Swift
import UIKit
|
|
import Capacitor
|
|
import CapacitorCommunitySqlite
|
|
|
|
@UIApplicationMain
|
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
|
|
var window: UIWindow?
|
|
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
// Initialize SQLite
|
|
//let sqlite = SQLite()
|
|
//sqlite.initialize()
|
|
|
|
// Override point for customization after application launch.
|
|
return true
|
|
}
|
|
|
|
func applicationWillResignActive(_ application: UIApplication) {
|
|
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
|
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
|
}
|
|
|
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
|
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
|
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
|
}
|
|
|
|
func applicationWillEnterForeground(_ application: UIApplication) {
|
|
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
|
}
|
|
|
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
|
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
|
|
|
// Check for shared image from Share Extension when app becomes active
|
|
checkForSharedImageOnActivation()
|
|
}
|
|
|
|
/**
|
|
* Check for shared image when app launches or becomes active
|
|
* This allows the app to detect shared images without requiring a deep link
|
|
*/
|
|
private func checkForSharedImageOnActivation() {
|
|
let appGroupIdentifier = "group.app.timesafari"
|
|
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
|
return
|
|
}
|
|
|
|
// Check if shared photo is ready
|
|
if userDefaults.bool(forKey: "sharedPhotoReady") {
|
|
// Clear the flag
|
|
userDefaults.removeObject(forKey: "sharedPhotoReady")
|
|
userDefaults.synchronize()
|
|
|
|
// Get and process shared image data
|
|
if let sharedData = getSharedImageData() {
|
|
writeSharedImageToTempFile(sharedData)
|
|
|
|
// Post notification for JavaScript to handle navigation
|
|
NotificationCenter.default.post(name: NSNotification.Name("SharedPhotoReady"), object: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write shared image data to temp file for JavaScript to read
|
|
*/
|
|
private func writeSharedImageToTempFile(_ sharedData: [String: String]) {
|
|
let fileManager = FileManager.default
|
|
guard let documentsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
|
return
|
|
}
|
|
|
|
let tempFileURL = documentsDir.appendingPathComponent("timesafari_shared_photo.json")
|
|
|
|
let jsonData: [String: String] = [
|
|
"base64": sharedData["base64"] ?? "",
|
|
"fileName": sharedData["fileName"] ?? ""
|
|
]
|
|
|
|
if let json = try? JSONSerialization.data(withJSONObject: jsonData, options: []) {
|
|
try? json.write(to: tempFileURL)
|
|
}
|
|
}
|
|
|
|
func applicationWillTerminate(_ application: UIApplication) {
|
|
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
|
}
|
|
|
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
|
// Check if this is a shared-photo deep link and store image data in a way JS can access
|
|
if url.scheme == "timesafari" && url.host == "shared-photo" {
|
|
// Try to get shared image from App Group and store it in a temp file that JS can read
|
|
// This is a workaround until the plugin is properly registered
|
|
if let sharedData = getSharedImageData() {
|
|
// Write to a temp file in the app's Documents directory that JavaScript can read via Filesystem plugin
|
|
let fileManager = FileManager.default
|
|
if let documentsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
|
|
let tempFileURL = documentsDir.appendingPathComponent("timesafari_shared_photo.json")
|
|
|
|
// Create JSON data
|
|
let jsonData: [String: String] = [
|
|
"base64": sharedData["base64"] ?? "",
|
|
"fileName": sharedData["fileName"] ?? ""
|
|
]
|
|
|
|
if let json = try? JSONSerialization.data(withJSONObject: jsonData, options: []) {
|
|
do {
|
|
try json.write(to: tempFileURL)
|
|
} catch {
|
|
// Error writing temp file - will be handled by JS layer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Called when the app was launched with a url. Feel free to add additional processing here,
|
|
// but if you want the App API to support tracking app url opens, make sure to keep this call
|
|
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
|
}
|
|
|
|
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
|
// Called when the app was launched with an activity, including Universal Links.
|
|
// Feel free to add additional processing here, but if you want the App API to support
|
|
// tracking app url opens, make sure to keep this call
|
|
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
|
}
|
|
|
|
/**
|
|
* Check for shared image from Share Extension
|
|
* Reads from App Group UserDefaults and returns shared image data if available
|
|
*
|
|
* @returns Dictionary with "base64" and "fileName" keys, or nil if no shared image
|
|
*/
|
|
func getSharedImageData() -> [String: String]? {
|
|
let appGroupIdentifier = "group.app.timesafari"
|
|
guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else {
|
|
return nil
|
|
}
|
|
|
|
guard let base64 = userDefaults.string(forKey: "sharedPhotoBase64"),
|
|
let fileName = userDefaults.string(forKey: "sharedPhotoFileName") else {
|
|
return nil
|
|
}
|
|
|
|
// Clear the shared data after reading
|
|
userDefaults.removeObject(forKey: "sharedPhotoBase64")
|
|
userDefaults.removeObject(forKey: "sharedPhotoFileName")
|
|
userDefaults.synchronize()
|
|
|
|
return ["base64": base64, "fileName": fileName]
|
|
}
|
|
|
|
}
|