diff --git a/App/App_iOS.swift b/App/App_iOS.swift
index 50b8acdc..0c06c9a4 100644
--- a/App/App_iOS.swift
+++ b/App/App_iOS.swift
@@ -17,6 +17,8 @@
import SwiftUI
import Combine
import UserNotifications
+import BackgroundTasks
+import os
@main
struct Kiwix: App {
@@ -33,7 +35,7 @@ struct Kiwix: App {
// MARK: - live activities
switch AppType.current {
case .kiwix:
- activityService = ActivityService()
+ activityService = ActivityService.shared()
case .custom:
activityService = nil
}
@@ -114,12 +116,69 @@ struct Kiwix: App {
}
private class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
+
/// Storing background download completion handler sent to application delegate
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
DownloadService.shared.backgroundCompletionHandler = completionHandler
}
+
+ func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
+ ) -> Bool {
+ registerBackgroundTask()
+ return true
+ }
+
+ // Background download task
+ func applicationDidFinishLaunching(_ application: UIApplication) {
+ registerBackgroundTask()
+ }
+ func applicationDidEnterBackground(_ application: UIApplication) {
+ registerBackgroundTask()
+ }
+
+ private func registerBackgroundTask() {
+ guard case .kiwix = AppType.current else { return }
+ let isRegistered = BGTaskScheduler.shared.register(
+ forTaskWithIdentifier: BackgroundDownloads.identifier,
+ using: .main) { [self] _ in
+ // update the live activities, if any
+ ActivityService.shared().start()
+ // reschedule
+ reScheduleBackgroundDownloadTask()
+ }
+ if isRegistered {
+ os_log("BackgroundDownloads registered", log: Log.DownloadService, type: .debug)
+ } else {
+ os_log("BackgroundDownloads registering failed: %s", log: Log.DownloadService, type: .error)
+ }
+ }
+
+ private func reScheduleBackgroundDownloadTask() {
+ do {
+ let date = BackgroundDownloads.nextDate()
+ let request = BGAppRefreshTaskRequest(identifier: BackgroundDownloads.identifier)
+ request.earliestBeginDate = date
+ os_log(
+ "BackgroundDownloads task re-scheduled for: %s",
+ log: Log.DownloadService,
+ type: .debug,
+ date.formatted()
+ )
+
+ try BGTaskScheduler.shared.submit(request)
+ } catch {
+ os_log(
+ "BackgroundDownloads re-schedule failed: %s",
+ log: Log.DownloadService,
+ type: .error,
+ error.localizedDescription
+ )
+ }
+ }
/// Handling file download complete notification
func userNotificationCenter(_ center: UNUserNotificationCenter,
diff --git a/Model/Downloads/BackgroundDownloads.swift b/Model/Downloads/BackgroundDownloads.swift
new file mode 100644
index 00000000..6df2201e
--- /dev/null
+++ b/Model/Downloads/BackgroundDownloads.swift
@@ -0,0 +1,28 @@
+// This file is part of Kiwix for iOS & macOS.
+//
+// Kiwix is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// any later version.
+//
+// Kiwix is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Kiwix; If not, see https://www.gnu.org/licenses/.
+
+#if os(iOS)
+import Foundation
+import BackgroundTasks
+
+enum BackgroundDownloads {
+ static let identifier = "org.kiwix.downloads_to_liveactivity"
+
+ static func nextDate() -> Date {
+ .now + 2 // after 2 seconds
+ }
+}
+
+#endif
diff --git a/Support/Info.plist b/Support/Info.plist
index 60f10513..3fedd627 100644
--- a/Support/Info.plist
+++ b/Support/Info.plist
@@ -6,7 +6,7 @@
id997079563
BGTaskSchedulerPermittedIdentifiers
- org.kiwix.library_refresh
+ org.kiwix.downloads_to_liveactivity
CFBundleDocumentTypes
diff --git a/Views/LiveActivity/ActivityService.swift b/Views/LiveActivity/ActivityService.swift
index 741e6ca9..025499e5 100644
--- a/Views/LiveActivity/ActivityService.swift
+++ b/Views/LiveActivity/ActivityService.swift
@@ -22,6 +22,7 @@ import QuartzCore
@MainActor
final class ActivityService {
+ private static var instance: ActivityService?
private var cancellables = Set()
private var activity: Activity?
private var lastUpdate = CACurrentMediaTime()
@@ -31,12 +32,30 @@ final class ActivityService {
private var isStarted: Bool = false
private var downloadTimes: [UUID: DownloadTime] = [:]
- init(
+ public static func shared(
publisher: @MainActor @escaping () -> CurrentValueSubject<[UUID: DownloadState], Never> = {
DownloadService.shared.progress.publisher
},
updateFrequency: Double = 2,
averageDownloadSpeedFromLastSeconds: Double = 30
+ ) -> ActivityService {
+ if let instance = Self.instance {
+ return instance
+ } else {
+ let instance = ActivityService(
+ publisher: publisher,
+ updateFrequency: updateFrequency,
+ averageDownloadSpeedFromLastSeconds: averageDownloadSpeedFromLastSeconds
+ )
+ Self.instance = instance
+ return instance
+ }
+ }
+
+ private init(
+ publisher: @MainActor @escaping () -> CurrentValueSubject<[UUID: DownloadState], Never>,
+ updateFrequency: Double,
+ averageDownloadSpeedFromLastSeconds: Double
) {
assert(updateFrequency > 0)
assert(averageDownloadSpeedFromLastSeconds > 0)