Tusker/Tusker/MainSceneDelegate.swift

248 lines
10 KiB
Swift

//
// MainSceneDelegate.swift
// Tusker
//
// Created by Shadowfacts on 1/6/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import CrashReporter
import MessageUI
import CoreData
class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private var launchActivity: NSUserActivity?
var rootViewController: TuskerRootViewController? {
window?.rootViewController as? TuskerRootViewController
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
launchActivity = activity
}
stateRestorationLogger.info("MainSceneDelegate.launchActivity = \(self.launchActivity?.activityType ?? "nil", privacy: .public)")
window = UIWindow(windowScene: windowScene)
if let report = AppDelegate.pendingCrashReport {
AppDelegate.pendingCrashReport = nil
handlePendingCrashReport(report, session: session)
} else {
showAppOrOnboardingUI(session: session)
if connectionOptions.urlContexts.count > 0 {
self.scene(scene, openURLContexts: connectionOptions.urlContexts)
}
}
window!.makeKeyAndVisible()
NotificationCenter.default.addObserver(self, selector: #selector(themePrefChanged), name: .themePreferenceChanged, object: nil)
themePrefChanged()
if let shortcutItem = connectionOptions.shortcutItem {
_ = AppShortcutItem.handle(shortcutItem)
}
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if URLContexts.count > 1 {
fatalError("Cannot open more than 1 URL")
}
let url = URLContexts.first!.url
if url.host == "x-callback-url" {
_ = XCBManager.handle(url: url)
} else if var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let rootViewController = rootViewController {
components.scheme = "https"
let query = components.string!
rootViewController.performSearch(query: query)
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
stateRestorationLogger.info("MainSceneDelegate.scene(_:continue:) called with \(userActivity.activityType, privacy: .public)")
_ = userActivity.handleResume(manager: UserActivityManager(scene: scene as! UIWindowScene))
}
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
completionHandler(AppShortcutItem.handle(shortcutItem))
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
Preferences.save()
DraftsManager.save()
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
Preferences.save()
DraftsManager.save()
}
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
if let mastodonController = window?.windowScene?.session.mastodonController {
return UserActivityManager.mainSceneActivity(accountID: mastodonController.accountInfo!.id)
} else {
return nil
}
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
if let rootVC = window?.rootViewController as? BackgroundableViewController {
rootVC.sceneDidEnterBackground()
}
if let context = scene.session.mastodonController?.persistentContainer.viewContext,
// if the user quickly opens and then closes the app, this may race with loading the persistent store, so in that event we skip cleanup/save
let psc = context.persistentStoreCoordinator,
!psc.persistentStores.isEmpty {
var minDate = Date()
minDate.addTimeInterval(-7 * 24 * 60 * 60)
let statusReq: NSFetchRequest<NSFetchRequestResult> = StatusMO.fetchRequest()
statusReq.predicate = NSPredicate(format: "(lastFetchedAt = nil) OR (lastFetchedAt < %@)", minDate as NSDate)
let deleteStatusReq = NSBatchDeleteRequest(fetchRequest: statusReq)
_ = try? context.execute(deleteStatusReq)
let accountReq: NSFetchRequest<NSFetchRequestResult> = AccountMO.fetchRequest()
accountReq.predicate = NSPredicate(format: "(lastFetchedAt = nil) OR (lastFetchedAt < %@)", minDate as NSDate)
let deleteAccountReq = NSBatchDeleteRequest(fetchRequest: accountReq)
_ = try? context.execute(deleteAccountReq)
try? context.save()
}
}
private func handlePendingCrashReport(_ report: PLCrashReport, session: UISceneSession) {
#if !DEBUG
guard MFMailComposeViewController.canSendMail() else {
print("Cannot send email")
showAppOrOnboardingUI(session: session)
return
}
window!.rootViewController = CrashReporterViewController.create(report: report, dismiss: {
self.showAppOrOnboardingUI()
})
#endif
}
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
let session = session ?? window!.windowScene!.session
if LocalData.shared.onboardingComplete {
let account: LocalData.UserAccountInfo
if let activity = launchActivity,
let activityAccount = UserActivityManager.getAccount(from: activity) {
account = activityAccount
} else {
account = LocalData.shared.getMostRecentAccount()!
}
if session.mastodonController == nil {
session.mastodonController = MastodonController.getForAccount(account)
}
activateAccount(account, animated: false)
if let activity = launchActivity,
activity.activityType != UserActivityType.mainScene.rawValue {
_ = activity.handleResume(manager: UserActivityManager(scene: window!.windowScene!))
}
} else {
window!.rootViewController = createOnboardingUI()
}
}
func activateAccount(_ account: LocalData.UserAccountInfo, animated: Bool) {
let oldMostRecentAccount = LocalData.shared.mostRecentAccountID
LocalData.shared.setMostRecentAccount(account)
window!.windowScene!.session.mastodonController = MastodonController.getForAccount(account)
let newRoot = createAppUI()
if let container = window?.rootViewController as? AccountSwitchingContainerViewController {
let direction: AccountSwitchingContainerViewController.AnimationDirection
if animated,
let oldIndex = LocalData.shared.accounts.firstIndex(where: { $0.id == oldMostRecentAccount }),
let newIndex = LocalData.shared.accounts.firstIndex(of: account) {
direction = newIndex > oldIndex ? .upwards : .downwards
} else {
direction = .none
}
container.setRoot(newRoot, animating: direction)
} else {
window!.rootViewController = AccountSwitchingContainerViewController(root: newRoot)
}
}
func logoutCurrent() {
LocalData.shared.removeAccount(LocalData.shared.getMostRecentAccount()!)
if LocalData.shared.onboardingComplete {
activateAccount(LocalData.shared.accounts.first!, animated: false)
} else {
window!.rootViewController = createOnboardingUI()
}
}
func createAppUI() -> TuskerRootViewController {
let mastodonController = window!.windowScene!.session.mastodonController!
mastodonController.getOwnAccount()
mastodonController.getOwnInstance()
return MainSplitViewController(mastodonController: mastodonController)
}
func createOnboardingUI() -> UIViewController {
let onboarding = OnboardingViewController()
onboarding.onboardingDelegate = self
return onboarding
}
@objc func themePrefChanged() {
window?.overrideUserInterfaceStyle = Preferences.shared.theme
}
func showAddAccount() {
rootViewController?.presentPreferences {
NotificationCenter.default.post(name: .addAccount, object: nil)
}
}
}
extension MainSceneDelegate: OnboardingViewControllerDelegate {
func didFinishOnboarding(account: LocalData.UserAccountInfo) {
activateAccount(account, animated: false)
}
}