damusApp.swift (7118B)
1 // 2 // damusApp.swift 3 // damus 4 // 5 // Created by William Casarin on 2022-04-01. 6 // 7 8 import Kingfisher 9 import SwiftUI 10 import StoreKit 11 12 @main 13 struct damusApp: App { 14 @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 15 var body: some Scene { 16 WindowGroup { 17 MainView(appDelegate: appDelegate) 18 } 19 } 20 } 21 22 struct MainView: View { 23 @State var needs_setup = false; 24 @State var keypair: Keypair? = nil; 25 @StateObject private var orientationTracker = OrientationTracker() 26 var appDelegate: AppDelegate 27 28 var body: some View { 29 Group { 30 if let kp = keypair, !needs_setup { 31 ContentView(keypair: kp, appDelegate: appDelegate) 32 .environmentObject(orientationTracker) 33 } else { 34 SetupView() 35 .onReceive(handle_notify(.login)) { notif in 36 needs_setup = false 37 keypair = get_saved_keypair() 38 if keypair == nil, let tempkeypair = notif.to_full()?.to_keypair() { 39 keypair = tempkeypair 40 } 41 } 42 } 43 } 44 .dynamicTypeSize(.xSmall ... .xxxLarge) 45 .onReceive(handle_notify(.logout)) { () in 46 try? clear_keypair() 47 keypair = nil 48 SuggestedHashtagsView.lastRefresh_hashtags.removeAll() 49 // We need to disconnect and reconnect to all relays when the user signs out 50 // This is to conform to NIP-42 and ensure we aren't persisting old connections 51 notify(.disconnect_relays) 52 } 53 .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in 54 orientationTracker.setDeviceMajorAxis() 55 } 56 .onAppear { 57 orientationTracker.setDeviceMajorAxis() 58 keypair = get_saved_keypair() 59 } 60 } 61 } 62 63 func registerNotificationCategories() { 64 // Define the communication category 65 let communicationCategory = UNNotificationCategory( 66 identifier: "COMMUNICATION", 67 actions: [], 68 intentIdentifiers: ["INSendMessageIntent"], 69 options: [] 70 ) 71 72 // Register the category with the notification center 73 UNUserNotificationCenter.current().setNotificationCategories([communicationCategory]) 74 } 75 76 class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { 77 var state: DamusState? = nil 78 79 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 80 UNUserNotificationCenter.current().delegate = self 81 SKPaymentQueue.default().add(StoreObserver.standard) 82 registerNotificationCategories() 83 migrateKingfisherCacheIfNeeded() 84 configureKingfisherCache() 85 return true 86 } 87 88 func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 89 guard let state else { 90 return 91 } 92 93 Task { 94 try await state.push_notification_client.set_device_token(new_device_token: deviceToken) 95 } 96 } 97 98 // Handle the notification in the foreground state 99 func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { 100 // Display the notification in the foreground 101 completionHandler([.banner, .list, .sound, .badge]) 102 } 103 104 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { 105 Log.info("App delegate is handling a push notification", for: .push_notifications) 106 let userInfo = response.notification.request.content.userInfo 107 guard let notification = LossyLocalNotification.from_user_info(user_info: userInfo) else { 108 Log.error("App delegate could not decode notification information", for: .push_notifications) 109 return 110 } 111 Log.info("App delegate notifying the app about the received push notification", for: .push_notifications) 112 Task { await QueueableNotify<LossyLocalNotification>.shared.add(item: notification) } 113 completionHandler() 114 } 115 116 private func migrateKingfisherCacheIfNeeded() { 117 let fileManager = FileManager.default 118 let defaults = UserDefaults.standard 119 let migrationKey = "KingfisherCacheMigrated" 120 121 // Check if migration has already been done 122 guard !defaults.bool(forKey: migrationKey) else { return } 123 124 // Get the default Kingfisher cache (before we override it) 125 let defaultCache = ImageCache.default 126 let oldCachePath = defaultCache.diskStorage.directoryURL.path 127 128 // New shared cache location 129 guard let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER) else { return } 130 let newCachePath = groupURL.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME).path 131 132 // Check if the old cache exists 133 if fileManager.fileExists(atPath: oldCachePath) { 134 do { 135 // Move the old cache to the new location 136 try fileManager.moveItem(atPath: oldCachePath, toPath: newCachePath) 137 print("Successfully migrated Kingfisher cache to \(newCachePath)") 138 } catch { 139 print("Failed to migrate cache: \(error)") 140 // Optionally, copy instead of move if you want to preserve the old cache as a fallback 141 do { 142 try fileManager.copyItem(atPath: oldCachePath, toPath: newCachePath) 143 print("Copied cache instead due to error") 144 } catch { 145 print("Failed to copy cache: \(error)") 146 } 147 } 148 } 149 150 // Mark migration as complete 151 defaults.set(true, forKey: migrationKey) 152 } 153 154 private func configureKingfisherCache() { 155 guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER) else { 156 return 157 } 158 159 let cachePath = groupURL.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME) 160 if let cache = try? ImageCache(name: "sharedCache", cacheDirectoryURL: cachePath) { 161 KingfisherManager.shared.cache = cache 162 } 163 } 164 } 165 166 class OrientationTracker: ObservableObject { 167 var deviceMajorAxis: CGFloat = 0 168 func setDeviceMajorAxis() { 169 let bounds = UIScreen.main.bounds 170 let height = max(bounds.height, bounds.width) /// device's longest dimension 171 let width = min(bounds.height, bounds.width) /// device's shortest dimension 172 let orientation = UIDevice.current.orientation 173 deviceMajorAxis = (orientation == .portrait || orientation == .unknown) ? height : width 174 } 175 }