diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 08b8cd611..05876207d 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -416,6 +416,7 @@ BA4499B325ED8AB0000C563E /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4499B225ED8AB0000C563E /* NoticeView.swift */; }; BA4499BC25ED95D0000C563E /* Notice.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4499BB25ED95D0000C563E /* Notice.swift */; }; BA4499C525ED95E5000C563E /* NoticeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4499C425ED95E5000C563E /* NoticeAction.swift */; }; + BA471060266821C0001A91A5 /* BackgroundRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA47105F266821C0001A91A5 /* BackgroundRefreshManager.swift */; }; BA4C6CFC264C744300B723A7 /* SeparatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6CFB264C744300B723A7 /* SeparatorsView.swift */; }; BA55124E2600210B00D8F882 /* TimerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA55124D2600210B00D8F882 /* TimerFactory.swift */; }; BA55B05A25F067DF0042582B /* NoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA55B05925F067DF0042582B /* NoticePresenter.swift */; }; @@ -954,6 +955,7 @@ BA4499B225ED8AB0000C563E /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = ""; }; BA4499BB25ED95D0000C563E /* Notice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notice.swift; sourceTree = ""; }; BA4499C425ED95E5000C563E /* NoticeAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeAction.swift; sourceTree = ""; }; + BA47105F266821C0001A91A5 /* BackgroundRefreshManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundRefreshManager.swift; sourceTree = ""; }; BA4C6CFB264C744300B723A7 /* SeparatorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorsView.swift; sourceTree = ""; }; BA55124D2600210B00D8F882 /* TimerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerFactory.swift; sourceTree = ""; }; BA55B05925F067DF0042582B /* NoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticePresenter.swift; sourceTree = ""; }; @@ -1880,6 +1882,7 @@ BA55124D2600210B00D8F882 /* TimerFactory.swift */, BAB01791260AAE93007A9CC3 /* NoticeFactory.swift */, BA4C6CFB264C744300B723A7 /* SeparatorsView.swift */, + BA47105F266821C0001A91A5 /* BackgroundRefreshManager.swift */, ); name = Tools; sourceTree = ""; @@ -2968,6 +2971,7 @@ BAA4856925D5E40900F3BDB9 /* SearchQuery+Simplenote.swift in Sources */, B56B357F1AC3565600B9F365 /* UITextView+Simplenote.m in Sources */, B5C9F71E193E75FE00FD2491 /* SPDebugViewController.m in Sources */, + BA471060266821C0001A91A5 /* BackgroundRefreshManager.swift in Sources */, B5A6166F2150855300CBE47B /* Preferences.m in Sources */, 46A3C98E17DFA81A002865AE /* SPObjectManager.m in Sources */, B5DF734F22A599D100602CE7 /* SPNotifications.m in Sources */, diff --git a/Simplenote/BackgroundRefreshManager.swift b/Simplenote/BackgroundRefreshManager.swift new file mode 100644 index 000000000..0048f0380 --- /dev/null +++ b/Simplenote/BackgroundRefreshManager.swift @@ -0,0 +1,101 @@ +import Foundation +import BackgroundTasks + +class BackgroundRefreshManager: NSObject { + private var timer: Timer? { + didSet { + oldValue?.invalidate() + } + } + + private var handler: (()->Void)? + + private func refreshTimer() { + // If refresh is not running there will be no handler + guard handler != nil else { + return + } + NSLog("Refresh Timer Called") + timer = Timer.scheduledTimer(timeInterval: Constants.timerTimeOut, target: self, selector: #selector(finishRefresh), userInfo: nil, repeats: false) + } + + @objc + private func finishRefresh() { + guard let handler = handler else { + return + } + NSLog("Finish Refresh Called") + + handler() + + self.handler = nil + self.timer = nil + } + + @objc + func onSimperiumChange() { + refreshTimer() + } +} + +@available(iOS 13.0, *) +extension BackgroundRefreshManager { + // MARK: - Background Fetch + // + @objc + func registerBackgroundRefreshTask() { + NSLog("Registered background task with identifier \(Constants.bgTaskIdentifier)") + BGTaskScheduler.shared.register(forTaskWithIdentifier: Constants.bgTaskIdentifier, using: .main) { task in + guard let task = task as? BGAppRefreshTask else { + return + } + self.handleAppRefresh(task: task) + } + } + + private func handleAppRefresh(task: BGAppRefreshTask) { + NSLog("Did fire handle app refresh") + handler = { + task.setTaskCompleted(success: true) + } + + task.expirationHandler = { [weak self] in + self?.finishRefresh() + } + + NSLog("Background refresh intiated") + scheduleAppRefresh() + + refreshTimer() + } + + @objc + func scheduleAppRefresh() { + guard BuildConfiguration.current == .debug else { + return + } + + NSLog("Background refresh scheduled") + let request = BGAppRefreshTaskRequest(identifier: Constants.bgTaskIdentifier) + request.earliestBeginDate = Date(timeIntervalSinceNow: Constants.earliestBeginDate) + do { + try BGTaskScheduler.shared.submit(request) + NSLog("Background refresh submitted") + } catch { + print("Couldn't schedule app refersh: \(error)") + } + } + + @objc + func cancelPendingRefreshTasks() { + BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: Constants.bgTaskIdentifier) + } +} + + +private struct Constants { + static let earliestBeginDate = TimeInterval(60) //30 minutes + static let bundleIdentifier = Bundle.main.bundleIdentifier ?? "com.codality.NotationalFlow" + static let bgTaskIdentifier = bundleIdentifier + ".refresh" + static let timerTimeOut = TimeInterval(8) +} diff --git a/Simplenote/SPAppDelegate+Extensions.swift b/Simplenote/SPAppDelegate+Extensions.swift index 2a7c4384d..8dcb14880 100644 --- a/Simplenote/SPAppDelegate+Extensions.swift +++ b/Simplenote/SPAppDelegate+Extensions.swift @@ -1,4 +1,5 @@ import Foundation +import BackgroundTasks // MARK: - Initialization diff --git a/Simplenote/SPAppDelegate.m b/Simplenote/SPAppDelegate.m index bf5577ea6..b89dfaaf2 100644 --- a/Simplenote/SPAppDelegate.m +++ b/Simplenote/SPAppDelegate.m @@ -44,10 +44,10 @@ @interface SPAppDelegate () @property (strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; @property (weak, nonatomic) SPModalActivityIndicator *signOutActivityIndicator; +@property (strong, nonatomic) BackgroundRefreshManager *refreshManager; @end - #pragma mark ================================================================================ #pragma mark Simplenote AppDelegate #pragma mark ================================================================================ @@ -166,6 +166,11 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [self showPasscodeLockIfNecessary]; } + // Register background refresh task to system + if (@available(iOS 13.0, *)) { + [self.refreshManager registerBackgroundRefreshTask]; + } + // Index (All of the) Spotlight Items if the user upgraded [self indexSpotlightItemsIfNeeded]; @@ -190,11 +195,20 @@ - (void)applicationDidEnterBackground:(UIApplication *)application [self showPasscodeLockIfNecessary]; [self cleanupScrollPositionCache]; + + // Schedule background refresh + if (@available(iOS 13.0, *)) { + [self.refreshManager scheduleAppRefresh]; + } } - (void)applicationWillEnterForeground:(UIApplication *)application { [self dismissPasscodeLockIfPossible]; + + if (@available(iOS 13.0, *)) { + [self.refreshManager cancelPendingRefreshTasks]; + } } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler @@ -420,6 +434,10 @@ - (void)setSelectedTag:(NSString *)selectedTag { - (void)bucket:(SPBucket *)bucket didChangeObjectForKey:(NSString *)key forChangeType:(SPBucketChangeType)change memberNames:(NSArray *)memberNames { + if (@available(iOS 13.0, *)) { + [self.refreshManager onSimperiumChange]; + } + if ([bucket isEqual:[_simperium notesBucket]]) { // Note change switch (change) { @@ -653,4 +671,20 @@ + (SPAppDelegate *)sharedDelegate return (SPAppDelegate *)[[UIApplication sharedApplication] delegate]; } + +#pragma mark ================================================================================ +#pragma mark Background Refresh +#pragma mark ================================================================================ + +- (BackgroundRefreshManager *)refreshManager +{ + if (_refreshManager != nil) { + return _refreshManager; + } + + _refreshManager = [[BackgroundRefreshManager alloc] init]; + + return _refreshManager; +} + @end diff --git a/Simplenote/Simplenote-Info.plist b/Simplenote/Simplenote-Info.plist index abc348b2b..f30c3c211 100644 --- a/Simplenote/Simplenote-Info.plist +++ b/Simplenote/Simplenote-Info.plist @@ -78,6 +78,10 @@ com.codality.NotationalFlow.newNote com.codality.NotationalFlow.openNote + UIBackgroundModes + + fetch + UILaunchStoryboardName LaunchScreen UIPrerenderedIcon @@ -86,6 +90,10 @@ armv7 + BGTaskSchedulerPermittedIdentifiers + + $(PRODUCT_BUNDLE_IDENTIFIER).refresh + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait