Files
crowd-funder-from-jason/ios/App/App/AppDelegate.swift
Jose Olarte III ae49c0e907 feat(ios): implement native share target for images
Implement iOS Share Extension to enable native image sharing from Photos
and other apps directly into TimeSafari. Users can now share images from
the iOS share sheet, which will open in SharedPhotoView for use as gifts
or profile pictures.

iOS Native Implementation:
- Add TimeSafariShareExtension target with ShareViewController
- Configure App Groups for data sharing between extension and main app
- Implement ShareViewController to process shared images and convert to base64
- Store shared image data in App Group UserDefaults
- Add ShareImageBridge utility to read shared data from App Group
- Update AppDelegate to handle shared-photo deep link and bridge data to JS

JavaScript Integration:
- Add checkAndStoreNativeSharedImage() in main.capacitor.ts to read shared
  images from native layer via temporary file bridge
- Convert base64 data to data URL format for compatibility with base64ToBlob
- Integrate with existing SharedPhotoView component
- Add "shared-photo" to deep link validation schema

Build System:
- Integrate Xcode 26 / CocoaPods compatibility workaround into build-ios.sh
- Add run_pod_install_with_workaround() for explicit pod install
- Add run_cap_sync_with_workaround() for Capacitor sync (which runs pod
  install internally)
- Automatically detect project format version 70 and apply workaround
- Remove standalone pod-install-workaround.sh script

Code Cleanup:
- Remove verbose debug logs from ShareViewController, AppDelegate, and
  main.capacitor.ts
- Retain essential logger calls for production debugging

Documentation:
- Add ios-share-extension-setup.md with manual Xcode setup instructions
- Add ios-share-extension-git-commit-guide.md for version control best practices
- Add ios-share-implementation-status.md tracking implementation progress
- Add native-share-target-implementation.md with overall architecture
- Add xcode-26-cocoapods-workaround.md documenting the compatibility issue

The implementation uses a temporary file bridge (AppDelegate writes to Documents
directory, JS reads via Capacitor Filesystem plugin) as a workaround for
Capacitor plugin auto-discovery issues. This can be improved in the future by
properly registering ShareImagePlugin in Capacitor's plugin registry.
2025-11-24 20:46:58 +08:00

107 lines
5.5 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.
}
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]
}
}