From 9238213d6c3459a6441a85ee38cb68526057149b Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 2 Jan 2024 14:50:27 +0100 Subject: [PATCH] feat: Refresh app in the background --- Mail/Helpers/AppAssembly.swift | 3 + Mail/MailApp.swift | 3 + MailCore/Cache/RefreshAppBackgroundTask.swift | 101 ++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 MailCore/Cache/RefreshAppBackgroundTask.swift diff --git a/Mail/Helpers/AppAssembly.swift b/Mail/Helpers/AppAssembly.swift index 7e6df11d3..581de3594 100644 --- a/Mail/Helpers/AppAssembly.swift +++ b/Mail/Helpers/AppAssembly.swift @@ -132,6 +132,9 @@ enum ApplicationAssembly { contactCache.countLimit = Constants.contactCacheExtensionMaxCount } return contactCache + }, + Factory(type: RefreshAppBackgroundTask.self) { _, _ in + RefreshAppBackgroundTask() } ] diff --git a/Mail/MailApp.swift b/Mail/MailApp.swift index 3ca298cf0..968289d74 100644 --- a/Mail/MailApp.swift +++ b/Mail/MailApp.swift @@ -36,6 +36,7 @@ struct MailApp: App { @LazyInjectService private var appLockHelper: AppLockHelper @LazyInjectService private var accountManager: AccountManager @LazyInjectService private var appLaunchCounter: AppLaunchCounter + @LazyInjectService private var refreshAppBackgroundTask: RefreshAppBackgroundTask @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @@ -49,6 +50,7 @@ struct MailApp: App { init() { DDLogInfo("Application starting in foreground ? \(UIApplication.shared.applicationState != .background)") + refreshAppBackgroundTask.register() } var body: some Scene { @@ -74,6 +76,7 @@ struct MailApp: App { navigationState.transitionToLockViewIfNeeded() UserDefaults.shared.openingUntilReview -= 1 case .background: + refreshAppBackgroundTask.scheduleForBackgroundLaunchIfNeeded() if UserDefaults.shared.isAppLockEnabled && navigationState.state != .appLocked { appLockHelper.setTime() } diff --git a/MailCore/Cache/RefreshAppBackgroundTask.swift b/MailCore/Cache/RefreshAppBackgroundTask.swift new file mode 100644 index 000000000..78ce201cd --- /dev/null +++ b/MailCore/Cache/RefreshAppBackgroundTask.swift @@ -0,0 +1,101 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program 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 + (at your option) any later version. + + This program 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 this program. If not, see . + */ + +import BackgroundTasks +import CocoaLumberjackSwift +import Foundation +import InfomaniakDI +import SwiftUI + +/// A type that represents a [task that can be executed in the background](https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app) +public protocol BackgroundTaskable { + /// Schedule the task for next background launch, only if needed + func scheduleForBackgroundLaunchIfNeeded() + + /// Schedule the task for next background launch + func scheduleForBackgroundLaunch() + + /// Ask the system to register the task + func register() + + /// The method that actually executes when background task is called + func run() async +} + +public class RefreshAppBackgroundTask: BackgroundTaskable { + let backgroundTaskIdentifier = "com.infomaniak.mail.background-refresh" + + @LazyInjectService var accountManager: AccountManager + + public init() { + // Exposed for DI + } + + public func scheduleForBackgroundLaunchIfNeeded() { + guard !accountManager.accounts.isEmpty else { return } + scheduleForBackgroundLaunch() + } + + public func scheduleForBackgroundLaunch() { + let request = BGAppRefreshTaskRequest(identifier: backgroundTaskIdentifier) + // Fetch no earlier than 15 minutes from now + request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + + do { + try BGTaskScheduler.shared.submit(request) + } catch { + DDLogError("Could not schedule app refresh: \(error)") + } + } + + public func run() async { + guard let currentMailboxManager = accountManager.currentMailboxManager, + let inboxFolder = currentMailboxManager.getFolder(with: .inbox), + inboxFolder.cursor != nil else { + // We do nothing if we don't have an initial cursor + BGTaskScheduler.shared.cancelAllTaskRequests() + return + } + + // Every time we execute a task we schedule a new task for next time + scheduleForBackgroundLaunch() + + await currentMailboxManager.refreshFolderContent(inboxFolder.freezeIfNeeded()) + + await NotificationsHelper.clearAlreadyReadNotifications() + + await NotificationsHelper.updateUnreadCountBadge() + } + + @available(iOS, deprecated: 16, message: "Use Scene.backgroundTask") + public func register() { + BGTaskScheduler.shared + .register(forTaskWithIdentifier: backgroundTaskIdentifier, using: nil) { task in + guard let refreshTask = task as? BGAppRefreshTask else { return } + + let handleAppRefresh = Task { + await self.run() + refreshTask.setTaskCompleted(success: true) + } + + refreshTask.expirationHandler = { + handleAppRefresh.cancel() + } + } + } +}