From 19f0ac303abba52348a8a4539347eb7588898cea Mon Sep 17 00:00:00 2001 From: Ryan Nystrom Date: Sat, 13 Oct 2018 22:38:06 -0400 Subject: [PATCH] move background handling into feed for better state control --- Classes/Issues/IssuesViewController.swift | 2 +- .../NotificationsViewController.swift | 17 ++--- ...lRequestReviewCommentsViewController.swift | 2 +- Classes/Systems/Feed.swift | 63 ++++++++++++++++++- Classes/Systems/ForegroundHandler.swift | 58 ----------------- .../BaseListViewController2.swift | 10 ++- Freetime.xcodeproj/project.pbxproj | 6 +- 7 files changed, 76 insertions(+), 82 deletions(-) delete mode 100644 Classes/Systems/ForegroundHandler.swift diff --git a/Classes/Issues/IssuesViewController.swift b/Classes/Issues/IssuesViewController.swift index 7210f3b2c..9833a7111 100644 --- a/Classes/Issues/IssuesViewController.swift +++ b/Classes/Issues/IssuesViewController.swift @@ -505,7 +505,7 @@ final class IssuesViewController: emptyView.delegate = self emptyView.button.isHidden = false return emptyView - case .loading, .loadingNext: + case .loading, .loadingNext, .initial: return nil } } diff --git a/Classes/Notifications/NotificationsViewController.swift b/Classes/Notifications/NotificationsViewController.swift index c2934df15..94a740105 100644 --- a/Classes/Notifications/NotificationsViewController.swift +++ b/Classes/Notifications/NotificationsViewController.swift @@ -13,14 +13,11 @@ import Squawk final class NotificationsViewController: BaseListViewController2, BaseListViewController2DataSource, -ForegroundHandlerDelegate, FlatCacheListener, TabNavRootViewControllerType, -BaseListViewController2EmptyDataSource -{ +BaseListViewController2EmptyDataSource { private let modelController: NotificationModelController - private let foreground = ForegroundHandler(threshold: 5 * 60) private let inboxType: InboxType private var notificationIDs = [String]() @@ -32,11 +29,13 @@ BaseListViewController2EmptyDataSource self.modelController = modelController self.inboxType = inboxType - super.init(emptyErrorMessage: NSLocalizedString("Cannot load your inbox.", comment: "")) + super.init( + emptyErrorMessage: NSLocalizedString("Cannot load your inbox.", comment: ""), + backgroundThreshold: 5 * 60 + ) self.dataSource = self self.emptyDataSource = self - self.foreground.delegate = self switch inboxType { case .all: title = NSLocalizedString("All", comment: "") @@ -283,12 +282,6 @@ BaseListViewController2EmptyDataSource }) } - // MARK: ForegroundHandlerDelegate - - func didForeground(handler: ForegroundHandler) { - feed.refreshHead() - } - // MARK: FlatCacheListener func flatCacheDidUpdate(cache: FlatCache, update: FlatCache.Update) { diff --git a/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift b/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift index 07d0e1d9c..6162f990e 100644 --- a/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift +++ b/Classes/PullRequestReviews/PullRequestReviewCommentsViewController.swift @@ -173,7 +173,7 @@ final class PullRequestReviewCommentsViewController: MessageViewController, emptyView.delegate = self emptyView.button.isHidden = false return emptyView - case .loadingNext: + case .loadingNext, .initial: return nil case .loading: return EmptyLoadingView() diff --git a/Classes/Systems/Feed.swift b/Classes/Systems/Feed.swift index baa20cc3a..5a7860c12 100644 --- a/Classes/Systems/Feed.swift +++ b/Classes/Systems/Feed.swift @@ -17,6 +17,7 @@ protocol FeedDelegate: class { final class Feed: NSObject, UIScrollViewDelegate { enum Status { + case initial case idle case loading case loadingNext @@ -25,24 +26,31 @@ final class Feed: NSObject, UIScrollViewDelegate { let swiftAdapter: ListSwiftAdapter let collectionView: UICollectionView - public private(set) var status: Status = .idle + public private(set) var status: Status = .initial private weak var delegate: FeedDelegate? private let feedRefresh = FeedRefresh() private let managesLayout: Bool private let loadingView = EmptyLoadingView() + private let backgroundThreshold: CFTimeInterval? + private var backgroundTime: CFTimeInterval? + init( viewController: UIViewController, delegate: FeedDelegate, collectionView: UICollectionView? = nil, - managesLayout: Bool = true + managesLayout: Bool = true, + backgroundThreshold: CFTimeInterval? = nil ) { self.swiftAdapter = ListSwiftAdapter(viewController: viewController) self.delegate = delegate self.managesLayout = managesLayout self.collectionView = collectionView ?? UICollectionView(frame: .zero, collectionViewLayout: ListCollectionViewLayout.basic()) + self.backgroundThreshold = backgroundThreshold + super.init() + self.adapter.scrollViewDelegate = self self.collectionView.alwaysBounceVertical = true @@ -50,6 +58,22 @@ final class Feed: NSObject, UIScrollViewDelegate { self.collectionView.refreshControl = feedRefresh.refreshControl self.collectionView.keyboardDismissMode = .onDrag feedRefresh.refreshControl.addTarget(self, action: #selector(Feed.onRefresh(sender:)), for: .valueChanged) + + if backgroundThreshold != nil { + let center = NotificationCenter.default + center.addObserver( + self, + selector: #selector(didBecomeActive), + name: .UIApplicationDidBecomeActive, + object: nil + ) + center.addObserver( + self, + selector: #selector(willResignActive), + name: .UIApplicationWillResignActive, + object: nil + ) + } } // MARK: Public API @@ -122,7 +146,7 @@ final class Feed: NSObject, UIScrollViewDelegate { // MARK: Private API private func refresh() { - guard status == .idle else { return } + guard status == .idle || status == .initial else { return } status = .loading delegate?.loadFromNetwork(feed: self) } @@ -150,4 +174,37 @@ final class Feed: NSObject, UIScrollViewDelegate { } } + // MARK: Notifications + + @objc func willResignActive() { + backgroundTime = CACurrentMediaTime() + } + + @objc func didBecomeActive() { + defer { backgroundTime = nil } + + let refresh: (Bool) -> Void = { head in + // there seems to be a bug where refreshing while still becoming active messes up the iOS 11 header and + // refresh control. put an artificial delay to let the system cool down? + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: { + if head { + self.refreshHead() + } else { + self.refresh() + } + }) + } + + // if foreground and haven't done an initial load, refresh + // can occur when process is still alive after a background fetch + if status == .initial { + // dont refresh head since spinner will still be shown + refresh(false) + } else if let backgroundTime = self.backgroundTime, + let backgroundThreshold = self.backgroundThreshold, + CACurrentMediaTime() - backgroundTime > backgroundThreshold { + refresh(true) + } + } + } diff --git a/Classes/Systems/ForegroundHandler.swift b/Classes/Systems/ForegroundHandler.swift deleted file mode 100644 index 401f04dd6..000000000 --- a/Classes/Systems/ForegroundHandler.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// ForegroundHandler.swift -// Freetime -// -// Created by Ryan Nystrom on 8/14/17. -// Copyright © 2017 Ryan Nystrom. All rights reserved. -// - -import Foundation - -protocol ForegroundHandlerDelegate: class { - func didForeground(handler: ForegroundHandler) -} - -final class ForegroundHandler { - - weak var delegate: ForegroundHandlerDelegate? - - private let threshold: CFTimeInterval - private var backgrounded: CFTimeInterval? - - init(threshold: TimeInterval) { - self.threshold = threshold - - let center = NotificationCenter.default - center.addObserver( - self, - selector: #selector(didBecomeActive), - name: .UIApplicationDidBecomeActive, - object: nil - ) - center.addObserver( - self, - selector: #selector(willResignActive), - name: .UIApplicationWillResignActive, - object: nil - ) - } - - // MARK: Private API - - @objc func willResignActive() { - backgrounded = CACurrentMediaTime() - } - - @objc func didBecomeActive() { - guard let backgrounded = self.backgrounded else { return } - self.backgrounded = nil - if CACurrentMediaTime() - backgrounded > threshold { - // there seems to be a bug where refreshing while still becoming active messes up the iOS 11 header and - // refresh control. put an artificial delay to let the system cool down? - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: { - self.delegate?.didForeground(handler: self) - }) - } - } - -} diff --git a/Classes/View Controllers/BaseListViewController2.swift b/Classes/View Controllers/BaseListViewController2.swift index 2f4329b9c..428b45abe 100644 --- a/Classes/View Controllers/BaseListViewController2.swift +++ b/Classes/View Controllers/BaseListViewController2.swift @@ -25,16 +25,22 @@ ListSwiftAdapterEmptyViewSource, EmptyViewDelegate { private let emptyErrorMessage: String + private let backgroundThreshold: CFTimeInterval? public weak var dataSource: BaseListViewController2DataSource? public weak var emptyDataSource: BaseListViewController2EmptyDataSource? - public private(set) lazy var feed: Feed = { Feed(viewController: self, delegate: self) }() + public private(set) lazy var feed: Feed = { Feed( + viewController: self, + delegate: self, + backgroundThreshold: backgroundThreshold + ) }() private var page: PageType? private var hasError = false - init(emptyErrorMessage: String) { + init(emptyErrorMessage: String, backgroundThreshold: CFTimeInterval? = nil) { self.emptyErrorMessage = emptyErrorMessage + self.backgroundThreshold = backgroundThreshold super.init(nibName: nil, bundle: nil) } diff --git a/Freetime.xcodeproj/project.pbxproj b/Freetime.xcodeproj/project.pbxproj index 1ea8381ff..bfd33a841 100644 --- a/Freetime.xcodeproj/project.pbxproj +++ b/Freetime.xcodeproj/project.pbxproj @@ -383,7 +383,6 @@ 29DB264A1FCA10A800C3D0C9 /* GithubHighlighting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DB26491FCA10A800C3D0C9 /* GithubHighlighting.swift */; }; 29DB5D152132227F00E408D9 /* InboxZeroLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DB5D142132227F00E408D9 /* InboxZeroLoader.swift */; }; 29DB5D192133418300E408D9 /* LocalNotificationsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DB5D182133418200E408D9 /* LocalNotificationsCache.swift */; }; - 29EB1EEF1F425E5100A200B4 /* ForegroundHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EB1EEE1F425E5100A200B4 /* ForegroundHandler.swift */; }; 29EDFE7C1F65C580005BCCEB /* SplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE7B1F65C580005BCCEB /* SplitViewTests.swift */; }; 29EDFE821F661562005BCCEB /* RepositoryReadmeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE811F661562005BCCEB /* RepositoryReadmeModel.swift */; }; 29EDFE841F661776005BCCEB /* RepositoryReadmeSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EDFE831F661776005BCCEB /* RepositoryReadmeSectionController.swift */; }; @@ -926,7 +925,6 @@ 29DB26491FCA10A800C3D0C9 /* GithubHighlighting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubHighlighting.swift; sourceTree = ""; }; 29DB5D142132227F00E408D9 /* InboxZeroLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxZeroLoader.swift; sourceTree = ""; }; 29DB5D182133418200E408D9 /* LocalNotificationsCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationsCache.swift; sourceTree = ""; }; - 29EB1EEE1F425E5100A200B4 /* ForegroundHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForegroundHandler.swift; sourceTree = ""; }; 29EDFE7B1F65C580005BCCEB /* SplitViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitViewTests.swift; sourceTree = ""; }; 29EDFE811F661562005BCCEB /* RepositoryReadmeModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryReadmeModel.swift; sourceTree = ""; }; 29EDFE831F661776005BCCEB /* RepositoryReadmeSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryReadmeSectionController.swift; sourceTree = ""; }; @@ -1601,7 +1599,6 @@ 297AE84E1EC0D58A00B44A1F /* Info.plist */, DC60C6D41F983DF800241271 /* IssueLabelCellTests.swift */, 29A476B11ED24D99005D0953 /* IssueTests.swift */, - BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */, 29C2950D1EC7B43B00D46CD2 /* ListKitTestCase.swift */, 29C295091EC7AFA500D46CD2 /* ListTestKit.swift */, 2977D8BE215AE12D0073F737 /* LocalNotificationCacheTests.swift */, @@ -1615,6 +1612,7 @@ DC60C6D21F983BB900241271 /* SignatureTests.swift */, 45A9D0381F9D3D6A00FD5AEF /* Snapshot Tests */, 29EDFE7B1F65C580005BCCEB /* SplitViewTests.swift */, + BDB6AA752165B8EA009BB73C /* SwitchBranches.swift */, 295F52A61EF1B9D2000B53CF /* Test.md */, ); path = FreetimeTests; @@ -1694,7 +1692,6 @@ DC78570F1F97F546009BADDA /* Debouncer.swift */, 29C167791ECA14F700439D62 /* Feed.swift */, 54AD5E8D1F24D953004A4BD6 /* FeedSelectionProviding.swift */, - 29EB1EEE1F425E5100A200B4 /* ForegroundHandler.swift */, 29C167731ECA0DBB00439D62 /* GithubAPIDateFormatter.swift */, 29C0E7061ECBC6C50051D756 /* GithubClient.swift */, 2981A8A31EFE9FC700E25EF1 /* GithubEmoji.swift */, @@ -2787,7 +2784,6 @@ 291929421F3EA8CD0012067B /* File.swift in Sources */, 299E86491EFD9DBB00E5FE70 /* FlexController.m in Sources */, 29F3A18620CBF99E00645CB7 /* NotificationModelController.swift in Sources */, - 29EB1EEF1F425E5100A200B4 /* ForegroundHandler.swift in Sources */, 29C167741ECA0DBB00439D62 /* GithubAPIDateFormatter.swift in Sources */, 290CA770216AE91300DE04F8 /* UITabBarController+SelectType.swift in Sources */, 294434E11FB1F2DA00050C06 /* BookmarkNavigationController.swift in Sources */,