damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }