2018-08-15 22:27:48 -04:00
//
// A p p D e l e g a t e . s w i f t
// T u s k e r
//
// C r e a t e d b y S h a d o w f a c t s o n 8 / 1 5 / 1 8 .
// C o p y r i g h t © 2 0 1 8 S h a d o w f a c t s . A l l r i g h t s r e s e r v e d .
//
import UIKit
2022-05-10 22:57:46 -04:00
import CoreData
2022-05-13 17:10:18 -04:00
import OSLog
2023-10-19 22:50:20 -04:00
#if canImport ( Sentry )
2022-10-30 17:10:58 -04:00
import Sentry
2023-10-19 22:50:20 -04:00
#endif
2023-03-05 14:35:25 -05:00
import UserAccounts
2023-04-16 13:31:10 -04:00
import ComposeUI
2023-04-18 19:47:49 -04:00
import TuskerPreferences
2024-04-08 09:48:40 -04:00
import PushNotifications
2023-04-18 19:47:49 -04:00
typealias Preferences = TuskerPreferences . Preferences
2022-05-13 17:10:18 -04:00
let stateRestorationLogger = Logger ( subsystem : Bundle . main . bundleIdentifier ! , category : " StateRestoration " )
2023-04-18 10:10:15 -04:00
private let logger = Logger ( subsystem : Bundle . main . bundleIdentifier ! , category : " AppDelegate " )
2018-08-15 22:27:48 -04:00
2024-01-26 11:02:40 -05:00
@ main
2018-08-15 22:27:48 -04:00
class AppDelegate : UIResponder , UIApplicationDelegate {
2020-06-20 23:11:35 -04:00
2018-08-15 22:27:48 -04:00
func application ( _ application : UIApplication , didFinishLaunchingWithOptions launchOptions : [ UIApplication . LaunchOptionsKey : Any ] ? ) -> Bool {
2023-10-19 22:50:20 -04:00
#if canImport ( Sentry )
2022-10-30 17:10:58 -04:00
configureSentry ( )
2023-10-19 22:50:20 -04:00
#endif
#if ! os ( visionOS )
2022-11-01 20:49:07 -04:00
swizzleStatusBar ( )
2023-02-02 23:02:11 -05:00
swizzlePresentationController ( )
2023-10-19 22:50:20 -04:00
#endif
2020-06-21 15:38:51 -04:00
2019-09-19 16:55:15 -04:00
AppShortcutItem . createItems ( for : application )
2020-06-21 15:38:51 -04:00
2022-05-10 22:57:46 -04:00
if let oldSavedData = SavedDataManager . load ( ) {
do {
for account in oldSavedData . accountIDs {
2023-03-05 14:35:25 -05:00
guard let account = UserAccountsManager . shared . getAccount ( id : account ) else {
2022-05-10 22:57:46 -04:00
continue
}
let controller = MastodonController . getForAccount ( account )
try oldSavedData . migrateToCoreData ( accountID : account . id , context : controller . persistentContainer . viewContext )
if controller . persistentContainer . viewContext . hasChanges {
try controller . persistentContainer . viewContext . save ( )
}
}
try SavedDataManager . destroy ( )
} catch {
// n o - o p
}
}
2023-04-16 13:31:10 -04:00
2023-05-04 18:33:06 -04:00
// m a k e s u r e t h e p e r s i s t e n t c o n t a i n e r i s i n i t i a l i z e d o n t h e m a i n t h r e a d
// o t h e r w i s e i n i t i a l i z i n g i t o n t h e b a c k g r o u n d t h r e a d c a n d e a d l o c k w i t h a c c e s s i n g i t o n t h e m a i n t h r e a d e l s e w h e r e
2024-07-24 20:42:35 -07:00
#if canImport ( Sentry )
DraftsPersistentContainer . captureError = { SentrySDK . capture ( error : $0 ) }
#endif
2023-05-04 18:33:06 -04:00
_ = DraftsPersistentContainer . shared
2023-04-16 13:31:10 -04:00
DispatchQueue . global ( qos : . userInitiated ) . async {
2024-04-13 18:44:43 -04:00
let documentsDirectory = FileManager . default . urls ( for : . documentDirectory , in : . userDomainMask ) . first !
2023-04-16 13:31:10 -04:00
let oldDraftsFile = documentsDirectory . appendingPathComponent ( " drafts " ) . appendingPathExtension ( " plist " )
2023-04-22 21:16:30 -04:00
let appGroupDraftsFile = FileManager . default . containerURL ( forSecurityApplicationGroupIdentifier : " group.space.vaccor.Tusker " ) ! . appendingPathComponent ( " drafts " ) . appendingPathExtension ( " plist " )
for url in [ oldDraftsFile , appGroupDraftsFile ] where FileManager . default . fileExists ( atPath : url . path ) {
DraftsPersistentContainer . shared . migrate ( from : url ) {
if case . failure ( let error ) = $0 {
2023-10-19 22:50:20 -04:00
#if canImport ( Sentry )
2023-04-22 21:16:30 -04:00
SentrySDK . capture ( error : error )
2023-10-19 22:50:20 -04:00
#endif
2023-04-22 21:16:30 -04:00
}
2023-04-16 13:31:10 -04:00
}
}
}
2023-04-18 19:47:49 -04:00
2023-05-15 23:26:03 -04:00
BackgroundManager . shared . registerHandlers ( )
2024-04-11 18:26:58 -04:00
initializePushNotifications ( )
2024-04-07 14:04:42 -04:00
2018-08-15 22:27:48 -04:00
return true
}
2020-06-20 23:11:35 -04:00
2023-10-19 22:50:20 -04:00
#if canImport ( Sentry )
2022-10-30 17:10:58 -04:00
private func configureSentry ( ) {
2024-04-07 14:04:42 -04:00
guard let info = Bundle . main . object ( forInfoDictionaryKey : " TuskerInfo " ) as ? [ String : Any ] ,
let dsn = info [ " SentryDSN " ] as ? String ,
2022-10-30 17:10:58 -04:00
! dsn . isEmpty else {
return
}
SentrySDK . start { options in
#if DEBUG
options . debug = true
options . environment = " dev "
#endif
// t h e ' / / ' i n t h e f u l l u r l c a n ' t b e e s c a p e d , s o w e h a v e t o a d d t h e s c h e m e b a c k
options . dsn = " https:// \( dsn ) "
options . enableSwizzling = false
2022-10-31 14:23:28 -04:00
// r e q u i r e d t o s u p p o r t r e l e a s e s / r e l e a s e h e a l t h
options . enableAutoSessionTracking = true
2023-01-27 22:39:15 -05:00
options . enableWatchdogTerminationTracking = false
options . enableAutoPerformanceTracing = false
2022-10-30 17:10:58 -04:00
options . enableNetworkTracking = false
options . enableAppHangTracking = false
2023-01-27 22:39:15 -05:00
options . enableCoreDataTracing = false
2022-10-31 14:23:28 -04:00
// w e d o n ' t c a r e a b o u t e v e n t s l i k e b a t t e r y , k e y b o a r d s h o w / h i d e
options . enableAutoBreadcrumbTracking = false
2023-01-27 22:39:15 -05:00
options . enableUserInteractionTracing = false
2024-08-11 21:27:33 -07:00
options . profilesSampleRate = nil
options . tracesSampleRate = nil
2022-10-30 18:17:53 -04:00
options . beforeSend = { event in
2022-10-31 14:23:28 -04:00
// j u s t n o , w h y w o u l d a n y o n e n e e d t h i s i n f o r m a t i o n
event . context ? . removeValue ( forKey : " culture " )
return Preferences . shared . reportErrorsAutomatically ? event : nil
2022-10-30 18:17:53 -04:00
}
2024-03-31 12:52:56 -04:00
if let clazz = NSClassFromString ( " SentryInstallation " ) ,
let objClazz = clazz as AnyObject as ? NSObject ,
let id = objClazz . perform ( Selector ( ( " idWithCacheDirectoryPath: " ) ) , with : options . cacheDirectoryPath ) . takeUnretainedValue ( ) as ? String {
logger . info ( " Initialized Sentry with installation/user ID: \( id , privacy : . public ) " )
}
2023-04-18 10:10:15 -04:00
}
2020-06-20 23:11:35 -04:00
}
2023-10-19 22:50:20 -04:00
#endif
2020-11-14 11:10:20 -05:00
override func buildMenu ( with builder : UIMenuBuilder ) {
if builder . system = = . main {
MenuController . buildMainMenu ( builder : builder )
}
}
2020-12-13 22:37:37 -05:00
func application ( _ application : UIApplication , configurationForConnecting connectingSceneSession : UISceneSession , options : UIScene . ConnectionOptions ) -> UISceneConfiguration {
let name = getSceneNameForActivity ( options . userActivities . first )
return UISceneConfiguration ( name : name , sessionRole : connectingSceneSession . role )
}
private func getSceneNameForActivity ( _ activity : NSUserActivity ? ) -> String {
guard let activity = activity ,
let type = UserActivityType ( rawValue : activity . activityType ) else {
return " main-scene "
}
switch type {
2021-06-06 14:55:04 -04:00
case . mainScene :
return " main-scene "
2022-05-13 17:10:18 -04:00
2020-12-13 22:37:37 -05:00
case . showConversation ,
. showTimeline ,
. checkNotifications ,
. search ,
. bookmarks ,
. myProfile ,
2024-04-12 22:47:11 -04:00
. showProfile ,
. showNotification :
2022-05-13 17:10:18 -04:00
if activity . displaysAuxiliaryScene {
stateRestorationLogger . info ( " Using auxiliary scene for \( type . rawValue , privacy : . public ) " )
return " auxiliary "
} else {
return " main-scene "
}
2020-12-13 22:37:37 -05:00
case . newPost :
return " compose "
}
}
2022-05-15 10:34:24 -04:00
2024-04-07 14:04:42 -04:00
func application ( _ application : UIApplication , didRegisterForRemoteNotificationsWithDeviceToken deviceToken : Data ) {
PushManager . shared . didRegisterForRemoteNotifications ( deviceToken : deviceToken )
}
func application ( _ application : UIApplication , didFailToRegisterForRemoteNotificationsWithError error : any Error ) {
PushManager . shared . didFailToRegisterForRemoteNotifications ( error : error )
}
2024-04-11 18:26:58 -04:00
private func initializePushNotifications ( ) {
UNUserNotificationCenter . current ( ) . delegate = self
2024-04-09 12:35:00 -04:00
Task {
2024-04-15 09:49:42 -04:00
#if canImport ( Sentry )
2024-04-09 12:35:00 -04:00
PushManager . captureError = { SentrySDK . capture ( error : $0 ) }
2024-04-15 09:49:42 -04:00
#endif
2024-04-09 12:35:00 -04:00
await PushManager . shared . updateIfNecessary ( updateSubscription : {
guard let account = UserAccountsManager . shared . getAccount ( id : $0 . accountID ) else {
return false
}
let mastodonController = MastodonController . getForAccount ( account )
do {
let result = try await mastodonController . updatePushSubscription ( subscription : $0 )
2024-05-27 13:33:00 -07:00
PushManager . logger . info ( " Updated push subscription \( result . id , privacy : . public ) on \( mastodonController . instanceURL ) with endpoint \( $0 . endpoint , privacy : . public ) " )
PushManager . logger . debug ( " New push subscription: \( String ( describing : result ) ) " )
2024-04-09 12:35:00 -04:00
return true
} catch {
PushManager . logger . error ( " Error updating push subscription: \( String ( describing : error ) ) " )
return false
}
} )
}
}
2023-10-19 22:50:20 -04:00
#if ! os ( visionOS )
2022-11-01 20:49:07 -04:00
private func swizzleStatusBar ( ) {
let selector = Selector ( ( " handleTapAction: " ) )
var originalIMP : IMP ?
let imp = imp_implementationWithBlock ( { ( self : UIStatusBarManager , sender : AnyObject ) in
let original = unsafeBitCast ( originalIMP ! , to : ( @ convention ( c ) ( UIStatusBarManager , Selector , AnyObject ) -> Void ) . self )
2023-02-15 19:34:18 -05:00
let exception = catchNSException {
guard let windowScene = self . perform ( Selector ( ( " windowScene " ) ) ) . takeUnretainedValue ( ) as ? UIWindowScene ,
let xPosition = sender . value ( forKey : " xPosition " ) as ? CGFloat ,
let delegate = windowScene . delegate as ? TuskerSceneDelegate else {
original ( self , selector , sender )
return
}
switch delegate . handleStatusBarTapped ( xPosition : xPosition ) {
case . stop :
return
case . continue :
original ( self , selector , sender )
}
2022-11-01 20:49:07 -04:00
}
2023-02-19 14:19:39 -05:00
if let exception {
SentrySDK . capture ( exception : exception )
}
2022-11-01 20:49:07 -04:00
} as @ convention ( block ) ( UIStatusBarManager , AnyObject ) -> Void )
originalIMP = class_replaceMethod ( UIStatusBarManager . self , selector , imp , " v@:@ " )
if originalIMP = = nil {
Logging . general . error ( " Unable to swizzle status bar manager " )
}
}
2023-02-02 23:02:11 -05:00
2023-09-09 11:45:54 -04:00
@ available ( iOS , obsoleted : 17.0 )
2023-02-02 23:02:11 -05:00
private func swizzlePresentationController ( ) {
2023-09-09 11:45:54 -04:00
guard # unavailable ( iOS 17.0 ) else {
return
}
2023-02-02 23:02:11 -05:00
var originalIMP : IMP ?
let imp = imp_implementationWithBlock ( { ( self : UIPresentationController ) in
let new = UITraitCollection ( pureBlackDarkMode : self . presentingViewController . traitCollection . pureBlackDarkMode )
if let existing = self . overrideTraitCollection {
self . overrideTraitCollection = UITraitCollection ( traitsFrom : [ existing , new ] )
} else {
self . overrideTraitCollection = new
}
let original = unsafeBitCast ( originalIMP ! , to : ( @ convention ( c ) ( UIPresentationController ) -> Void ) . self )
original ( self )
} as ( @ convention ( block ) ( UIPresentationController ) -> Void ) )
let sel = Selector ( [ " Necessary " , " If " , " Traits " , " update " , " _ " ] . reversed ( ) . joined ( ) )
originalIMP = class_replaceMethod ( UIPresentationController . self , sel , imp , " v@: " )
if originalIMP = = nil {
Logging . general . error ( " Unable to swizzle presentation controller " )
}
}
2023-10-19 22:50:20 -04:00
#endif
2023-02-02 23:02:11 -05:00
2018-08-15 22:27:48 -04:00
}
2024-04-11 18:26:58 -04:00
extension AppDelegate : UNUserNotificationCenterDelegate {
func userNotificationCenter ( _ center : UNUserNotificationCenter , openSettingsFor notification : UNNotification ? ) {
let mainSceneDelegate : MainSceneDelegate
if let delegate = UIApplication . shared . activeScene ? . delegate as ? MainSceneDelegate {
mainSceneDelegate = delegate
} else if let scene = UIApplication . shared . connectedScenes . first ( where : { $0 . delegate is MainSceneDelegate } ) {
mainSceneDelegate = scene . delegate as ! MainSceneDelegate
} else if let accountID = UserAccountsManager . shared . mostRecentAccountID {
let activity = UserActivityManager . mainSceneActivity ( accountID : accountID )
activity . addUserInfoEntries ( from : [ " showNotificationsPreferences " : true ] )
UIApplication . shared . requestSceneSessionActivation ( nil , userActivity : activity , options : nil )
return
} else {
// w i t h o u t a n a c c o u n t , w e c a n ' t d o a n y t h i n g
return
}
mainSceneDelegate . showNotificationsPreferences ( )
}
2024-04-12 22:47:11 -04:00
func userNotificationCenter ( _ center : UNUserNotificationCenter , willPresent notification : UNNotification , withCompletionHandler completionHandler : @ escaping ( UNNotificationPresentationOptions ) -> Void ) {
completionHandler ( . banner )
}
func userNotificationCenter ( _ center : UNUserNotificationCenter , didReceive response : UNNotificationResponse , withCompletionHandler completionHandler : @ escaping ( ) -> Void ) {
let userInfo = response . notification . request . content . userInfo
guard let notificationID = userInfo [ " notificationID " ] as ? String ,
let accountID = userInfo [ " accountID " ] as ? String ,
let account = UserAccountsManager . shared . getAccount ( id : accountID ) else {
return
}
if let scene = response . targetScene ,
let delegate = scene . delegate as ? MainSceneDelegate ,
let rootViewController = delegate . rootViewController {
let mastodonController = MastodonController . getForAccount ( account )
2024-08-22 14:32:01 -04:00
// i f t h e s c e n e i s a l r e a d y a c t i v e , t h e n w e a n i m a t e t h i n g s
let animated = scene . activationState = = . foregroundActive
2024-04-12 22:47:11 -04:00
2024-08-22 14:32:01 -04:00
delegate . activateAccount ( account , animated : animated )
rootViewController . runNavigation ( animated : animated ) { navigation in
navigation . select ( route : . notifications )
let vc = NotificationLoadingViewController ( notificationID : notificationID , mastodonController : mastodonController )
navigation . push ( viewController : vc )
}
2024-04-12 22:47:11 -04:00
} else {
let activity = UserActivityManager . showNotificationActivity ( id : notificationID , accountID : accountID )
2024-06-02 10:10:16 -07:00
if #available ( iOS 17.0 , * ) {
let request = UISceneSessionActivationRequest ( userActivity : activity )
UIApplication . shared . activateSceneSession ( for : request )
} else {
UIApplication . shared . requestSceneSessionActivation ( nil , userActivity : activity , options : nil )
}
2024-04-12 22:47:11 -04:00
}
completionHandler ( )
}
2024-04-11 18:26:58 -04:00
}