Restore article scroll position on iOS

This commit is contained in:
Maurice Parker 2021-09-13 01:11:23 -05:00
parent 87700325cf
commit 5001d82355
4 changed files with 67 additions and 27 deletions

View File

@ -23,5 +23,7 @@ struct UserInfoKey {
static let readArticlesFilterStateKeys = "readArticlesFilterStateKey"
static let readArticlesFilterStateValues = "readArticlesFilterStateValue"
static let selectedFeedsState = "selectedFeedsState"
static let isShowingExtractedArticle = "isShowingExtractedArticle"
static let articleWindowScrollY = "articleWindowScrollY"
}

View File

@ -311,7 +311,11 @@ class ArticleViewController: UIViewController {
func openInAppBrowser() {
currentWebViewController?.openInAppBrowser()
}
}
func setScrollPosition(isShowingExtractedArticle: Bool, articleWindowScrollY: Int) {
currentWebViewController?.setScrollPosition(isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
}
}
// MARK: Find in Article

View File

@ -63,7 +63,8 @@ class WebViewController: UIViewController {
let scrollPositionQueue = CoalescingQueue(name: "Article Scroll Position", interval: 0.3, maxInterval: 0.3)
var windowScrollY = 0
private var restoreWindowScrollY: Int?
override func viewDidLoad() {
super.viewDidLoad()
@ -129,6 +130,27 @@ class WebViewController: UIViewController {
}
func setScrollPosition(isShowingExtractedArticle: Bool, articleWindowScrollY: Int) {
if isShowingExtractedArticle {
switch articleExtractor?.state {
case .ready:
restoreWindowScrollY = articleWindowScrollY
startArticleExtractor()
case .complete:
windowScrollY = articleWindowScrollY
loadWebView()
case .processing:
restoreWindowScrollY = articleWindowScrollY
default:
restoreWindowScrollY = articleWindowScrollY
startArticleExtractor()
}
} else {
windowScrollY = articleWindowScrollY
loadWebView()
}
}
func focus() {
webView?.becomeFirstResponder()
}
@ -277,6 +299,9 @@ extension WebViewController: ArticleExtractorDelegate {
func articleExtractionDidComplete(extractedArticle: ExtractedArticle) {
if articleExtractor?.state != .cancelled {
self.extractedArticle = extractedArticle
if let restoreWindowScrollY = restoreWindowScrollY {
windowScrollY = restoreWindowScrollY
}
isShowingExtractedArticle = true
loadWebView()
articleExtractorButtonState = .on
@ -591,6 +616,7 @@ private extension WebViewController {
}
func startArticleExtractor() {
guard articleExtractor == nil else { return }
if let link = article?.preferredLink, let extractor = ArticleExtractor(link) {
extractor.delegate = self
extractor.process()

View File

@ -109,8 +109,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
var stateRestorationActivity: NSUserActivity {
let activity = activityManager.stateRestorationActivity
var userInfo = activity.userInfo == nil ? [AnyHashable: Any]() : activity.userInfo
userInfo![UserInfoKey.windowState] = windowState()
var userInfo = activity.userInfo ?? [AnyHashable: Any]()
userInfo[UserInfoKey.windowState] = windowState()
let articleState = articleViewController?.currentState
userInfo[UserInfoKey.isShowingExtractedArticle] = articleState?.isShowingExtractedArticle ?? false
userInfo[UserInfoKey.articleWindowScrollY] = articleState?.windowScrollY ?? 0
activity.userInfo = userInfo
return activity
}
@ -169,7 +175,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
// * Once the article has loaded, navigate to the iPad home screen
// * While in landscape, select a feed and then select an article
// * Install a fresh build of NNW to an iPad simulator (11 or 12.9' will do) running iPadOS 15
private var deferredFeedAndArticleSelect: (feedIndexPath: IndexPath, articleID: String)?
private var deferredFeedAndArticleSelect: (feedIndexPath: IndexPath, articleID: String, isShowingExtractedArticle: Bool, articleWindowScrollY: Int)?
var timelineMiddleIndexPath: IndexPath?
@ -453,9 +459,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
rebuildBackingStores(initialLoad: true)
treeControllerDelegate.resetFilterExceptions()
if let (feedIndexPath, articleID) = deferredFeedAndArticleSelect {
if let (feedIndexPath, articleID, isShowingExtractedArticle, articleWindowScrollY) = deferredFeedAndArticleSelect {
selectFeed(indexPath: feedIndexPath) {
self.selectArticleInCurrentFeed(articleID)
self.articleViewController?.setScrollPosition(isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
}
}
}
@ -2254,14 +2261,14 @@ private extension SceneCoordinator {
guard let userInfo = userInfo else { return }
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String,
let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String,
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String,
let accountNode = findAccountNode(accountID: accountID, accountName: accountName),
let account = accountNode.representedObject as? Account else {
return
}
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String,
let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String,
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String,
let accountNode = findAccountNode(accountID: accountID, accountName: accountName),
let account = accountNode.representedObject as? Account else {
return
}
exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: articleID)
@ -2280,20 +2287,21 @@ private extension SceneCoordinator {
func restoreFeedSelection(_ userInfo: [AnyHashable : Any], accountID: String, webFeedID: String, articleID: String) -> Bool {
guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable],
let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo) else {
return false
}
let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo),
let isShowingExtractedArticle = userInfo[UserInfoKey.isShowingExtractedArticle] as? Bool,
let articleWindowScrollY = userInfo[UserInfoKey.articleWindowScrollY] as? Int else {
return false
}
switch feedIdentifier {
case .smartFeed:
guard let smartFeed = SmartFeedsController.shared.find(by: feedIdentifier) else { return false }
if let indexPath = indexPathFor(smartFeed) {
selectFeed(indexPath: indexPath) {
self.selectArticleInCurrentFeed(articleID)
if let smartFeedNode = nodeFor(feedID: feedIdentifier) {
let found = deferSelectFeedAndArticle(feedNode: smartFeedNode, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
if found {
treeControllerDelegate.addFilterException(feedIdentifier)
}
treeControllerDelegate.addFilterException(feedIdentifier)
return true
return found
}
case .script:
@ -2304,7 +2312,7 @@ private extension SceneCoordinator {
let folderNode = findFolderNode(folderName: folderName, beginningAt: accountNode) else {
return false
}
let found = selectFeedAndArticle(feedNode: folderNode, articleID: articleID)
let found = deferSelectFeedAndArticle(feedNode: folderNode, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
if found {
treeControllerDelegate.addFilterException(feedIdentifier)
}
@ -2314,7 +2322,7 @@ private extension SceneCoordinator {
guard let accountNode = findAccountNode(accountID: accountID), let webFeedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else {
return false
}
let found = selectFeedAndArticle(feedNode: webFeedNode, articleID: articleID)
let found = deferSelectFeedAndArticle(feedNode: webFeedNode, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
if found {
treeControllerDelegate.addFilterException(feedIdentifier)
if let folder = webFeedNode.parent?.representedObject as? Folder, let folderFeedID = folder.feedID {
@ -2354,9 +2362,9 @@ private extension SceneCoordinator {
return nil
}
func selectFeedAndArticle(feedNode: Node, articleID: String) -> Bool {
func deferSelectFeedAndArticle(feedNode: Node, articleID: String, isShowingExtractedArticle: Bool, articleWindowScrollY: Int) -> Bool {
if let feedIndexPath = indexPathFor(feedNode) {
deferredFeedAndArticleSelect = (feedIndexPath, articleID)
deferredFeedAndArticleSelect = (feedIndexPath, articleID, isShowingExtractedArticle, articleWindowScrollY)
return true
}
return false