Restore article scroll position on iOS
This commit is contained in:
parent
87700325cf
commit
5001d82355
|
@ -23,5 +23,7 @@ struct UserInfoKey {
|
||||||
static let readArticlesFilterStateKeys = "readArticlesFilterStateKey"
|
static let readArticlesFilterStateKeys = "readArticlesFilterStateKey"
|
||||||
static let readArticlesFilterStateValues = "readArticlesFilterStateValue"
|
static let readArticlesFilterStateValues = "readArticlesFilterStateValue"
|
||||||
static let selectedFeedsState = "selectedFeedsState"
|
static let selectedFeedsState = "selectedFeedsState"
|
||||||
|
static let isShowingExtractedArticle = "isShowingExtractedArticle"
|
||||||
|
static let articleWindowScrollY = "articleWindowScrollY"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,7 +311,11 @@ class ArticleViewController: UIViewController {
|
||||||
|
|
||||||
func openInAppBrowser() {
|
func openInAppBrowser() {
|
||||||
currentWebViewController?.openInAppBrowser()
|
currentWebViewController?.openInAppBrowser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setScrollPosition(isShowingExtractedArticle: Bool, articleWindowScrollY: Int) {
|
||||||
|
currentWebViewController?.setScrollPosition(isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Find in Article
|
// MARK: Find in Article
|
||||||
|
|
|
@ -63,7 +63,8 @@ class WebViewController: UIViewController {
|
||||||
|
|
||||||
let scrollPositionQueue = CoalescingQueue(name: "Article Scroll Position", interval: 0.3, maxInterval: 0.3)
|
let scrollPositionQueue = CoalescingQueue(name: "Article Scroll Position", interval: 0.3, maxInterval: 0.3)
|
||||||
var windowScrollY = 0
|
var windowScrollY = 0
|
||||||
|
private var restoreWindowScrollY: Int?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.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() {
|
func focus() {
|
||||||
webView?.becomeFirstResponder()
|
webView?.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
@ -277,6 +299,9 @@ extension WebViewController: ArticleExtractorDelegate {
|
||||||
func articleExtractionDidComplete(extractedArticle: ExtractedArticle) {
|
func articleExtractionDidComplete(extractedArticle: ExtractedArticle) {
|
||||||
if articleExtractor?.state != .cancelled {
|
if articleExtractor?.state != .cancelled {
|
||||||
self.extractedArticle = extractedArticle
|
self.extractedArticle = extractedArticle
|
||||||
|
if let restoreWindowScrollY = restoreWindowScrollY {
|
||||||
|
windowScrollY = restoreWindowScrollY
|
||||||
|
}
|
||||||
isShowingExtractedArticle = true
|
isShowingExtractedArticle = true
|
||||||
loadWebView()
|
loadWebView()
|
||||||
articleExtractorButtonState = .on
|
articleExtractorButtonState = .on
|
||||||
|
@ -591,6 +616,7 @@ private extension WebViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startArticleExtractor() {
|
func startArticleExtractor() {
|
||||||
|
guard articleExtractor == nil else { return }
|
||||||
if let link = article?.preferredLink, let extractor = ArticleExtractor(link) {
|
if let link = article?.preferredLink, let extractor = ArticleExtractor(link) {
|
||||||
extractor.delegate = self
|
extractor.delegate = self
|
||||||
extractor.process()
|
extractor.process()
|
||||||
|
|
|
@ -109,8 +109,14 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||||
|
|
||||||
var stateRestorationActivity: NSUserActivity {
|
var stateRestorationActivity: NSUserActivity {
|
||||||
let activity = activityManager.stateRestorationActivity
|
let activity = activityManager.stateRestorationActivity
|
||||||
var userInfo = activity.userInfo == nil ? [AnyHashable: Any]() : activity.userInfo
|
var userInfo = activity.userInfo ?? [AnyHashable: Any]()
|
||||||
userInfo![UserInfoKey.windowState] = windowState()
|
|
||||||
|
userInfo[UserInfoKey.windowState] = windowState()
|
||||||
|
|
||||||
|
let articleState = articleViewController?.currentState
|
||||||
|
userInfo[UserInfoKey.isShowingExtractedArticle] = articleState?.isShowingExtractedArticle ?? false
|
||||||
|
userInfo[UserInfoKey.articleWindowScrollY] = articleState?.windowScrollY ?? 0
|
||||||
|
|
||||||
activity.userInfo = userInfo
|
activity.userInfo = userInfo
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
@ -169,7 +175,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||||
// * Once the article has loaded, navigate to the iPad home screen
|
// * Once the article has loaded, navigate to the iPad home screen
|
||||||
// * While in landscape, select a feed and then select an article
|
// * 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
|
// * 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?
|
var timelineMiddleIndexPath: IndexPath?
|
||||||
|
|
||||||
|
@ -453,9 +459,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||||
rebuildBackingStores(initialLoad: true)
|
rebuildBackingStores(initialLoad: true)
|
||||||
treeControllerDelegate.resetFilterExceptions()
|
treeControllerDelegate.resetFilterExceptions()
|
||||||
|
|
||||||
if let (feedIndexPath, articleID) = deferredFeedAndArticleSelect {
|
if let (feedIndexPath, articleID, isShowingExtractedArticle, articleWindowScrollY) = deferredFeedAndArticleSelect {
|
||||||
selectFeed(indexPath: feedIndexPath) {
|
selectFeed(indexPath: feedIndexPath) {
|
||||||
self.selectArticleInCurrentFeed(articleID)
|
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 userInfo = userInfo else { return }
|
||||||
|
|
||||||
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
|
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
|
||||||
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
|
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
|
||||||
let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String,
|
let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String,
|
||||||
let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String,
|
let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String,
|
||||||
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String,
|
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String,
|
||||||
let accountNode = findAccountNode(accountID: accountID, accountName: accountName),
|
let accountNode = findAccountNode(accountID: accountID, accountName: accountName),
|
||||||
let account = accountNode.representedObject as? Account else {
|
let account = accountNode.representedObject as? Account else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: articleID)
|
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 {
|
func restoreFeedSelection(_ userInfo: [AnyHashable : Any], accountID: String, webFeedID: String, articleID: String) -> Bool {
|
||||||
guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable],
|
guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable],
|
||||||
let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo) else {
|
let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo),
|
||||||
return false
|
let isShowingExtractedArticle = userInfo[UserInfoKey.isShowingExtractedArticle] as? Bool,
|
||||||
}
|
let articleWindowScrollY = userInfo[UserInfoKey.articleWindowScrollY] as? Int else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
switch feedIdentifier {
|
switch feedIdentifier {
|
||||||
|
|
||||||
case .smartFeed:
|
case .smartFeed:
|
||||||
guard let smartFeed = SmartFeedsController.shared.find(by: feedIdentifier) else { return false }
|
if let smartFeedNode = nodeFor(feedID: feedIdentifier) {
|
||||||
if let indexPath = indexPathFor(smartFeed) {
|
let found = deferSelectFeedAndArticle(feedNode: smartFeedNode, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
|
||||||
selectFeed(indexPath: indexPath) {
|
if found {
|
||||||
self.selectArticleInCurrentFeed(articleID)
|
treeControllerDelegate.addFilterException(feedIdentifier)
|
||||||
}
|
}
|
||||||
treeControllerDelegate.addFilterException(feedIdentifier)
|
return found
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case .script:
|
case .script:
|
||||||
|
@ -2304,7 +2312,7 @@ private extension SceneCoordinator {
|
||||||
let folderNode = findFolderNode(folderName: folderName, beginningAt: accountNode) else {
|
let folderNode = findFolderNode(folderName: folderName, beginningAt: accountNode) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let found = selectFeedAndArticle(feedNode: folderNode, articleID: articleID)
|
let found = deferSelectFeedAndArticle(feedNode: folderNode, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
|
||||||
if found {
|
if found {
|
||||||
treeControllerDelegate.addFilterException(feedIdentifier)
|
treeControllerDelegate.addFilterException(feedIdentifier)
|
||||||
}
|
}
|
||||||
|
@ -2314,7 +2322,7 @@ private extension SceneCoordinator {
|
||||||
guard let accountNode = findAccountNode(accountID: accountID), let webFeedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else {
|
guard let accountNode = findAccountNode(accountID: accountID), let webFeedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let found = selectFeedAndArticle(feedNode: webFeedNode, articleID: articleID)
|
let found = deferSelectFeedAndArticle(feedNode: webFeedNode, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
|
||||||
if found {
|
if found {
|
||||||
treeControllerDelegate.addFilterException(feedIdentifier)
|
treeControllerDelegate.addFilterException(feedIdentifier)
|
||||||
if let folder = webFeedNode.parent?.representedObject as? Folder, let folderFeedID = folder.feedID {
|
if let folder = webFeedNode.parent?.representedObject as? Folder, let folderFeedID = folder.feedID {
|
||||||
|
@ -2354,9 +2362,9 @@ private extension SceneCoordinator {
|
||||||
return nil
|
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) {
|
if let feedIndexPath = indexPathFor(feedNode) {
|
||||||
deferredFeedAndArticleSelect = (feedIndexPath, articleID)
|
deferredFeedAndArticleSelect = (feedIndexPath, articleID, isShowingExtractedArticle, articleWindowScrollY)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
Loading…
Reference in New Issue