2019-05-09 12:45:30 -07:00
|
|
|
//
|
|
|
|
|
// AppDelegate.swift
|
|
|
|
|
// AltStore
|
|
|
|
|
//
|
|
|
|
|
// Created by Riley Testut on 5/9/19.
|
|
|
|
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import UIKit
|
2019-06-04 18:29:50 -07:00
|
|
|
import UserNotifications
|
2019-07-01 17:13:55 -07:00
|
|
|
import AVFoundation
|
2020-09-08 12:29:44 -07:00
|
|
|
import Intents
|
2019-05-09 12:45:30 -07:00
|
|
|
|
2020-09-03 16:39:08 -07:00
|
|
|
import AltStoreCore
|
2019-05-30 17:10:50 -07:00
|
|
|
import AltSign
|
|
|
|
|
import Roxas
|
2022-11-02 17:58:59 -07:00
|
|
|
import EmotionalDamage
|
2019-05-30 17:10:50 -07:00
|
|
|
|
2023-12-07 16:58:39 -06:00
|
|
|
import Nuke
|
|
|
|
|
|
2023-03-02 15:56:41 -06:00
|
|
|
extension UIApplication: LegacyBackgroundFetching {}
|
|
|
|
|
|
2019-09-19 14:43:26 -07:00
|
|
|
extension AppDelegate
|
|
|
|
|
{
|
2022-12-30 16:51:36 -05:00
|
|
|
static let openPatreonSettingsDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".OpenPatreonSettingsDeepLinkNotification")
|
|
|
|
|
static let importAppDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ImportAppDeepLinkNotification")
|
|
|
|
|
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
|
|
|
|
|
|
|
|
|
|
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
|
|
|
|
|
|
2019-09-27 17:39:36 -07:00
|
|
|
static let importAppDeepLinkURLKey = "fileURL"
|
2020-05-15 15:11:17 -07:00
|
|
|
static let appBackupResultKey = "result"
|
2020-08-28 12:15:15 -07:00
|
|
|
static let addSourceDeepLinkURLKey = "sourceURL"
|
2019-09-19 14:43:26 -07:00
|
|
|
}
|
|
|
|
|
|
2019-05-09 12:45:30 -07:00
|
|
|
@UIApplicationMain
|
2023-01-04 09:52:12 -05:00
|
|
|
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
2019-05-09 12:45:30 -07:00
|
|
|
|
|
|
|
|
var window: UIWindow?
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2023-03-02 16:53:36 -06:00
|
|
|
private let intentHandler = IntentHandler()
|
|
|
|
|
private let viewAppIntentHandler = ViewAppIntentHandler()
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2024-12-29 03:12:59 +05:30
|
|
|
public let consoleLog = ConsoleLog()
|
|
|
|
|
|
2019-05-20 21:24:53 +02:00
|
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
|
|
|
|
{
|
2025-01-02 22:20:37 +05:30
|
|
|
// navigation bar buttons spacing is too much (so hack it to use minimal spacing)
|
|
|
|
|
// this is swift-5 specific behavior and might change
|
|
|
|
|
// https://stackoverflow.com/a/64988363/11971304
|
|
|
|
|
//
|
|
|
|
|
// Warning: this affects all screens through out the app, and basically overrides storyboard
|
|
|
|
|
let stackViewAppearance = UIStackView.appearance(whenContainedInInstancesOf: [UINavigationBar.self])
|
|
|
|
|
stackViewAppearance.spacing = -8 // adjust as needed
|
2024-12-29 03:12:59 +05:30
|
|
|
|
2025-01-02 22:20:37 +05:30
|
|
|
|
|
|
|
|
// TODO: @mahee96: the capture is started, but when application sleeps/pushed to bg
|
|
|
|
|
// terminate might be called even if the application is not really closed?
|
|
|
|
|
// so the log gets rotated making write to a new log on wake up here
|
|
|
|
|
// thereby splitting the logs of current session into multiple ones
|
|
|
|
|
//
|
|
|
|
|
// Fix this later!
|
2024-12-29 03:12:59 +05:30
|
|
|
// start logging to console immediately on startup
|
|
|
|
|
consoleLog.startCapturing()
|
|
|
|
|
|
2024-12-08 03:17:20 +05:30
|
|
|
// Override point for customization after application launch.
|
2024-12-13 14:15:55 +05:30
|
|
|
// UserDefaults.standard.setValue(true, forKey: "com.apple.CoreData.MigrationDebug")
|
|
|
|
|
// UserDefaults.standard.setValue(true, forKey: "com.apple.CoreData.SQLDebug")
|
2024-12-08 03:17:20 +05:30
|
|
|
|
2020-09-16 12:09:12 -07:00
|
|
|
// Register default settings before doing anything else.
|
|
|
|
|
UserDefaults.registerDefaults()
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2024-12-08 03:17:20 +05:30
|
|
|
|
2024-06-17 09:43:25 +10:00
|
|
|
|
2020-07-13 17:59:52 -07:00
|
|
|
DatabaseManager.shared.start { (error) in
|
|
|
|
|
if let error = error
|
|
|
|
|
{
|
|
|
|
|
print("Failed to start DatabaseManager. Error:", error as Any)
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
print("Started DatabaseManager.")
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-03-31 14:31:34 -07:00
|
|
|
AnalyticsManager.shared.start()
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-07-24 12:23:54 -07:00
|
|
|
self.setTintColor()
|
2023-12-07 16:58:39 -06:00
|
|
|
self.prepareImageCache()
|
2024-12-07 17:45:09 +05:30
|
|
|
|
|
|
|
|
// TODO: @mahee96: find if we need to start em_proxy as in altstore?
|
|
|
|
|
// start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
|
|
|
|
|
2022-09-09 17:47:49 -05:00
|
|
|
SecureValueTransformer.register()
|
|
|
|
|
|
2019-06-04 18:29:50 -07:00
|
|
|
if UserDefaults.standard.firstLaunch == nil
|
|
|
|
|
{
|
2019-06-06 14:46:23 -07:00
|
|
|
Keychain.shared.reset()
|
2019-06-04 18:29:50 -07:00
|
|
|
UserDefaults.standard.firstLaunch = Date()
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-03 23:47:47 -07:00
|
|
|
UserDefaults.standard.preferredServerID = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.serverID) as? String
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2024-12-08 02:50:40 +05:30
|
|
|
// #if DEBUG || BETA
|
2019-09-19 22:20:10 -07:00
|
|
|
UserDefaults.standard.isDebugModeEnabled = true
|
2024-12-08 02:50:40 +05:30
|
|
|
// #endif
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-06-04 18:29:50 -07:00
|
|
|
self.prepareForBackgroundFetch()
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-05-09 12:45:30 -07:00
|
|
|
return true
|
|
|
|
|
}
|
2022-11-02 17:58:59 -07:00
|
|
|
|
2019-05-30 17:10:50 -07:00
|
|
|
func applicationDidEnterBackground(_ application: UIApplication)
|
2022-09-09 17:47:49 -05:00
|
|
|
{
|
|
|
|
|
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
|
2024-12-07 17:45:09 +05:30
|
|
|
// TODO: @mahee96: find if we need to stop em_proxy as in altstore?
|
|
|
|
|
// stop_em_proxy()
|
2022-09-09 17:47:49 -05:00
|
|
|
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
|
|
|
|
|
|
|
|
|
|
let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo)
|
|
|
|
|
DatabaseManager.shared.purgeLoggedErrors(before: midnightOneMonthAgo) { result in
|
|
|
|
|
switch result
|
|
|
|
|
{
|
|
|
|
|
case .success: break
|
|
|
|
|
case .failure(let error): print("[ALTLog] Failed to purge logged errors before \(midnightOneMonthAgo).", error)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-11-02 17:58:59 -07:00
|
|
|
|
2022-09-09 17:47:49 -05:00
|
|
|
}
|
2019-05-09 12:45:30 -07:00
|
|
|
|
2019-05-30 17:10:50 -07:00
|
|
|
func applicationWillEnterForeground(_ application: UIApplication)
|
|
|
|
|
{
|
2019-06-04 18:29:50 -07:00
|
|
|
AppManager.shared.update()
|
2022-11-08 14:15:09 -05:00
|
|
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
2024-12-07 17:45:09 +05:30
|
|
|
|
|
|
|
|
PatreonAPI.shared.refreshPatreonAccount()
|
2019-05-09 12:45:30 -07:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-19 14:43:26 -07:00
|
|
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
|
|
|
|
{
|
|
|
|
|
return self.open(url)
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any?
|
|
|
|
|
{
|
2020-09-15 15:19:12 -07:00
|
|
|
switch intent
|
|
|
|
|
{
|
|
|
|
|
case is RefreshAllIntent: return self.intentHandler
|
|
|
|
|
case is ViewAppIntent: return self.viewAppIntentHandler
|
|
|
|
|
default: return nil
|
|
|
|
|
}
|
2020-09-08 12:29:44 -07:00
|
|
|
}
|
2024-12-29 03:12:59 +05:30
|
|
|
|
|
|
|
|
func applicationWillTerminate(_ application: UIApplication) {
|
|
|
|
|
// Stop console logging and clean up resources
|
|
|
|
|
consoleLog.stopCapturing()
|
|
|
|
|
}
|
2019-06-04 18:29:50 -07:00
|
|
|
}
|
2019-05-09 12:45:30 -07:00
|
|
|
|
2020-07-13 17:59:52 -07:00
|
|
|
extension AppDelegate
|
|
|
|
|
{
|
|
|
|
|
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
|
|
|
|
|
{
|
|
|
|
|
// Called when a new scene session is being created.
|
|
|
|
|
// Use this method to select a configuration to create the new scene with.
|
|
|
|
|
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-07-13 17:59:52 -07:00
|
|
|
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)
|
|
|
|
|
{
|
|
|
|
|
// Called when the user discards a scene session.
|
|
|
|
|
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
|
|
|
|
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 12:23:54 -07:00
|
|
|
private extension AppDelegate
|
|
|
|
|
{
|
|
|
|
|
func setTintColor()
|
|
|
|
|
{
|
2019-09-19 11:29:10 -07:00
|
|
|
self.window?.tintColor = .altPrimary
|
2019-07-24 12:23:54 -07:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2023-12-07 16:58:39 -06:00
|
|
|
func prepareImageCache()
|
|
|
|
|
{
|
|
|
|
|
// Avoid caching responses twice.
|
|
|
|
|
DataLoader.sharedUrlCache.diskCapacity = 0
|
|
|
|
|
|
|
|
|
|
let pipeline = ImagePipeline { configuration in
|
|
|
|
|
do
|
|
|
|
|
{
|
2024-12-13 14:15:55 +05:30
|
|
|
let dataCache = try DataCache(name: "io.sidestore.Nuke")
|
2023-12-07 16:58:39 -06:00
|
|
|
dataCache.sizeLimit = 512 * 1024 * 1024 // 512MB
|
|
|
|
|
|
|
|
|
|
configuration.dataCache = dataCache
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
Logger.main.error("Failed to create image disk cache. Falling back to URL cache. \(error.localizedDescription, privacy: .public)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImagePipeline.shared = pipeline
|
|
|
|
|
|
|
|
|
|
if let dataCache = ImagePipeline.shared.configuration.dataCache as? DataCache, #available(iOS 15, *)
|
|
|
|
|
{
|
|
|
|
|
Logger.main.info("Current image cache size: \(dataCache.totalSize.formatted(.byteCount(style: .file)), privacy: .public)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 14:43:26 -07:00
|
|
|
func open(_ url: URL) -> Bool
|
|
|
|
|
{
|
2019-09-27 17:39:36 -07:00
|
|
|
if url.isFileURL
|
|
|
|
|
{
|
|
|
|
|
guard url.pathExtension.lowercased() == "ipa" else { return false }
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-27 17:39:36 -07:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: url])
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-27 17:39:36 -07:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
|
2020-05-15 15:11:17 -07:00
|
|
|
guard let host = components.host?.lowercased() else { return false }
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
switch host
|
|
|
|
|
{
|
|
|
|
|
case "patreon":
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
NotificationCenter.default.post(name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
return true
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
case "appbackupresponse":
|
|
|
|
|
let result: Result<Void, Error>
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
switch url.path.lowercased()
|
|
|
|
|
{
|
|
|
|
|
case "/success": result = .success(())
|
|
|
|
|
case "/failure":
|
|
|
|
|
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name] = $1.value } ?? [:]
|
|
|
|
|
guard
|
|
|
|
|
let errorDomain = queryItems["errorDomain"],
|
|
|
|
|
let errorCodeString = queryItems["errorCode"], let errorCode = Int(errorCodeString),
|
|
|
|
|
let errorDescription = queryItems["errorDescription"]
|
|
|
|
|
else { return false }
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
let error = NSError(domain: errorDomain, code: errorCode, userInfo: [NSLocalizedDescriptionKey: errorDescription])
|
|
|
|
|
result = .failure(error)
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
default: return false
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
NotificationCenter.default.post(name: AppDelegate.appBackupDidFinish, object: nil, userInfo: [AppDelegate.appBackupResultKey: result])
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
return true
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-17 23:44:36 -07:00
|
|
|
case "install":
|
|
|
|
|
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
|
|
|
|
guard let downloadURLString = queryItems["url"], let downloadURL = URL(string: downloadURLString) else { return false }
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-17 23:44:36 -07:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
NotificationCenter.default.post(name: AppDelegate.importAppDeepLinkNotification, object: nil, userInfo: [AppDelegate.importAppDeepLinkURLKey: downloadURL])
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-17 23:44:36 -07:00
|
|
|
return true
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-08-28 12:15:15 -07:00
|
|
|
case "source":
|
|
|
|
|
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
|
|
|
|
guard let sourceURLString = queryItems["url"], let sourceURL = URL(string: sourceURLString) else { return false }
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-08-28 12:15:15 -07:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL])
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-08-28 12:15:15 -07:00
|
|
|
return true
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-05-15 15:11:17 -07:00
|
|
|
default: return false
|
2019-09-27 17:39:36 -07:00
|
|
|
}
|
2019-09-19 14:43:26 -07:00
|
|
|
}
|
|
|
|
|
}
|
2019-07-24 12:23:54 -07:00
|
|
|
}
|
|
|
|
|
|
2019-06-04 18:29:50 -07:00
|
|
|
extension AppDelegate
|
|
|
|
|
{
|
|
|
|
|
private func prepareForBackgroundFetch()
|
|
|
|
|
{
|
2019-06-21 11:20:03 -07:00
|
|
|
// "Fetch" every hour, but then refresh only those that need to be refreshed (so we don't drain the battery).
|
2023-03-02 15:56:41 -06:00
|
|
|
(UIApplication.shared as LegacyBackgroundFetching).setMinimumBackgroundFetchInterval(1 * 60 * 60)
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-06-04 18:29:50 -07:00
|
|
|
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (success, error) in
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-07-01 17:14:51 -07:00
|
|
|
#if DEBUG
|
|
|
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-07-01 17:14:51 -07:00
|
|
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
|
|
|
|
|
{
|
|
|
|
|
let tokenParts = deviceToken.map { data -> String in
|
|
|
|
|
return String(format: "%02.2hhx", data)
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-07-01 17:14:51 -07:00
|
|
|
let token = tokenParts.joined()
|
|
|
|
|
print("Push Token:", token)
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-07-01 17:14:51 -07:00
|
|
|
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
|
|
|
|
{
|
|
|
|
|
self.application(application, performFetchWithCompletionHandler: completionHandler)
|
2019-05-09 12:45:30 -07:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-07-31 13:35:12 -07:00
|
|
|
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
2019-06-04 18:29:50 -07:00
|
|
|
{
|
2020-09-08 12:29:44 -07:00
|
|
|
if UserDefaults.standard.isBackgroundRefreshEnabled && !UserDefaults.standard.presentedLaunchReminderNotification
|
2019-09-05 11:51:49 -07:00
|
|
|
{
|
2020-09-08 12:29:44 -07:00
|
|
|
let threeHours: TimeInterval = 3 * 60 * 60
|
|
|
|
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: threeHours, repeats: false)
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
let content = UNMutableNotificationContent()
|
|
|
|
|
content.title = NSLocalizedString("App Refresh Tip", comment: "")
|
2022-11-05 23:50:07 -07:00
|
|
|
content.body = NSLocalizedString("The more you open SideStore, the more chances it's given to refresh apps in the background.", comment: "")
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
|
|
|
|
|
UNUserNotificationCenter.current().add(request)
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
UserDefaults.standard.presentedLaunchReminderNotification = true
|
2019-09-05 11:51:49 -07:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-06-21 11:33:12 -07:00
|
|
|
BackgroundTaskManager.shared.performExtendedBackgroundTask { (taskResult, taskCompletionHandler) in
|
2019-07-24 13:16:53 -07:00
|
|
|
if let error = taskResult.error
|
|
|
|
|
{
|
|
|
|
|
print("Error starting extended background task. Aborting.", error)
|
2019-07-31 13:35:12 -07:00
|
|
|
backgroundFetchCompletionHandler(.failed)
|
2020-09-08 12:29:44 -07:00
|
|
|
taskCompletionHandler()
|
2019-07-24 13:16:53 -07:00
|
|
|
return
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-07-31 13:35:12 -07:00
|
|
|
if !DatabaseManager.shared.isStarted
|
|
|
|
|
{
|
|
|
|
|
DatabaseManager.shared.start() { (error) in
|
2020-09-08 12:29:44 -07:00
|
|
|
if error != nil
|
2019-07-31 13:35:12 -07:00
|
|
|
{
|
|
|
|
|
backgroundFetchCompletionHandler(.failed)
|
2020-09-08 12:29:44 -07:00
|
|
|
taskCompletionHandler()
|
2019-07-31 13:35:12 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-09-08 12:29:44 -07:00
|
|
|
self.performBackgroundFetch { (backgroundFetchResult) in
|
|
|
|
|
backgroundFetchCompletionHandler(backgroundFetchResult)
|
|
|
|
|
} refreshAppsCompletionHandler: { (refreshAppsResult) in
|
|
|
|
|
taskCompletionHandler()
|
|
|
|
|
}
|
2019-07-31 13:35:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-09-08 12:29:44 -07:00
|
|
|
self.performBackgroundFetch { (backgroundFetchResult) in
|
|
|
|
|
backgroundFetchCompletionHandler(backgroundFetchResult)
|
|
|
|
|
} refreshAppsCompletionHandler: { (refreshAppsResult) in
|
|
|
|
|
taskCompletionHandler()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
func performBackgroundFetch(backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
|
|
|
|
refreshAppsCompletionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
|
|
|
|
|
{
|
|
|
|
|
self.fetchSources { (result) in
|
|
|
|
|
switch result
|
|
|
|
|
{
|
|
|
|
|
case .failure: backgroundFetchCompletionHandler(.failed)
|
|
|
|
|
case .success: backgroundFetchCompletionHandler(.newData)
|
2019-07-31 13:35:12 -07:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
if !UserDefaults.standard.isBackgroundRefreshEnabled
|
|
|
|
|
{
|
|
|
|
|
refreshAppsCompletionHandler(.success([:]))
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
guard UserDefaults.standard.isBackgroundRefreshEnabled else { return }
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
|
|
|
|
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
|
|
|
|
|
AppManager.shared.backgroundRefresh(installedApps, completionHandler: refreshAppsCompletionHandler)
|
2019-07-31 13:35:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private extension AppDelegate
|
|
|
|
|
{
|
2020-09-08 12:29:44 -07:00
|
|
|
func fetchSources(completionHandler: @escaping (Result<Set<Source>, Error>) -> Void)
|
2019-07-31 13:35:12 -07:00
|
|
|
{
|
2020-03-24 13:27:44 -07:00
|
|
|
AppManager.shared.fetchSources() { (result) in
|
2019-09-05 11:51:49 -07:00
|
|
|
do
|
|
|
|
|
{
|
2020-09-08 12:29:44 -07:00
|
|
|
let (sources, context) = try result.get()
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2022-11-16 17:14:39 -06:00
|
|
|
let previousUpdatesFetchRequest = InstalledApp.supportedUpdatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
2019-09-05 11:51:49 -07:00
|
|
|
previousUpdatesFetchRequest.includesPendingChanges = false
|
|
|
|
|
previousUpdatesFetchRequest.resultType = .dictionaryResultType
|
2023-05-18 14:51:26 -05:00
|
|
|
previousUpdatesFetchRequest.propertiesToFetch = [#keyPath(InstalledApp.bundleIdentifier),
|
|
|
|
|
#keyPath(InstalledApp.storeApp.latestSupportedVersion.version),
|
|
|
|
|
#keyPath(InstalledApp.storeApp.latestSupportedVersion._buildVersion)]
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-06 14:41:30 -07:00
|
|
|
let previousNewsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NSFetchRequestResult>
|
|
|
|
|
previousNewsItemsFetchRequest.includesPendingChanges = false
|
|
|
|
|
previousNewsItemsFetchRequest.resultType = .dictionaryResultType
|
|
|
|
|
previousNewsItemsFetchRequest.propertiesToFetch = [#keyPath(NewsItem.identifier)]
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-05 11:51:49 -07:00
|
|
|
let previousUpdates = try context.fetch(previousUpdatesFetchRequest) as! [[String: String]]
|
2019-09-06 14:41:30 -07:00
|
|
|
let previousNewsItems = try context.fetch(previousNewsItemsFetchRequest) as! [[String: String]]
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-05 11:51:49 -07:00
|
|
|
try context.save()
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2022-11-16 17:14:39 -06:00
|
|
|
let updatesFetchRequest = InstalledApp.supportedUpdatesFetchRequest()
|
2019-09-06 14:41:30 -07:00
|
|
|
let newsItemsFetchRequest = NewsItem.fetchRequest() as NSFetchRequest<NewsItem>
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-05 11:51:49 -07:00
|
|
|
let updates = try context.fetch(updatesFetchRequest)
|
2019-09-06 14:41:30 -07:00
|
|
|
let newsItems = try context.fetch(newsItemsFetchRequest)
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-05 11:51:49 -07:00
|
|
|
for update in updates
|
2019-07-31 13:35:12 -07:00
|
|
|
{
|
2022-11-16 17:14:39 -06:00
|
|
|
guard let storeApp = update.storeApp, let latestSupportedVersion = storeApp.latestSupportedVersion, latestSupportedVersion.isSupported else { continue }
|
|
|
|
|
|
|
|
|
|
if let previousUpdate = previousUpdates.first(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier })
|
|
|
|
|
{
|
2023-05-18 14:51:26 -05:00
|
|
|
// An update for this app was already available, so check whether the version or build version is different.
|
|
|
|
|
guard let previousVersion = previousUpdate[#keyPath(InstalledApp.storeApp.latestSupportedVersion.version)] else { continue }
|
|
|
|
|
|
|
|
|
|
// previousUpdate might not contain buildVersion, but if it does then map empty string to nil to match AppVersion.
|
|
|
|
|
let previousBuildVersion = previousUpdate[#keyPath(InstalledApp.storeApp.latestSupportedVersion._buildVersion)].map { $0.isEmpty ? nil : "" }
|
|
|
|
|
|
|
|
|
|
// Only show notification if previous latestSupportedVersion does not _exactly_ match current latestSupportedVersion.
|
|
|
|
|
guard previousVersion != latestSupportedVersion.version || previousBuildVersion != latestSupportedVersion.buildVersion else { continue }
|
2022-11-16 17:14:39 -06:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-05 11:51:49 -07:00
|
|
|
let content = UNMutableNotificationContent()
|
|
|
|
|
content.title = NSLocalizedString("New Update Available", comment: "")
|
2023-05-18 14:51:26 -05:00
|
|
|
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, latestSupportedVersion.localizedVersion)
|
2019-09-21 22:30:01 -07:00
|
|
|
content.sound = .default
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-05 11:51:49 -07:00
|
|
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
|
|
|
UNUserNotificationCenter.current().add(request)
|
|
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-06 14:41:30 -07:00
|
|
|
for newsItem in newsItems
|
|
|
|
|
{
|
|
|
|
|
guard !previousNewsItems.contains(where: { $0[#keyPath(NewsItem.identifier)] == newsItem.identifier }) else { continue }
|
|
|
|
|
guard !newsItem.isSilent else { continue }
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-06 14:41:30 -07:00
|
|
|
let content = UNMutableNotificationContent()
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-06 14:41:30 -07:00
|
|
|
if let app = newsItem.storeApp
|
|
|
|
|
{
|
|
|
|
|
content.title = String(format: NSLocalizedString("%@ News", comment: ""), app.name)
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-11-05 23:50:07 -07:00
|
|
|
content.title = NSLocalizedString("SideStore News", comment: "")
|
2019-09-06 14:41:30 -07:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-06 14:41:30 -07:00
|
|
|
content.body = newsItem.title
|
2019-09-21 22:30:01 -07:00
|
|
|
content.sound = .default
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2019-09-06 14:41:30 -07:00
|
|
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
|
|
|
UNUserNotificationCenter.current().add(request)
|
|
|
|
|
}
|
2019-09-05 11:51:49 -07:00
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
UIApplication.shared.applicationIconBadgeNumber = updates.count
|
2019-07-31 13:35:12 -07:00
|
|
|
}
|
2022-09-09 17:47:49 -05:00
|
|
|
|
2020-09-08 12:29:44 -07:00
|
|
|
completionHandler(.success(sources))
|
2019-07-31 13:35:12 -07:00
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
2020-09-08 12:29:44 -07:00
|
|
|
print("Error fetching apps:", error)
|
|
|
|
|
completionHandler(.failure(error))
|
2019-07-24 13:16:53 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-09 12:45:30 -07:00
|
|
|
}
|