diff --git a/Multiplatform/Shared/SceneModel.swift b/Multiplatform/Shared/SceneModel.swift index 29a6f1064..d7db42236 100644 --- a/Multiplatform/Shared/SceneModel.swift +++ b/Multiplatform/Shared/SceneModel.swift @@ -55,7 +55,10 @@ final class SceneModel: ObservableObject { /// Goes to the next unread item found in Sidebar and Timeline order, top to bottom func goToNextUnread() { - + if !timelineModel.goToNextUnread() { + sidebarModel.goToNextUnread() + timelineModel.goToNextUnread() + } } // MARK: Article Management API diff --git a/Multiplatform/Shared/Sidebar/SidebarItem.swift b/Multiplatform/Shared/Sidebar/SidebarItem.swift index a848cc887..01ae100d2 100644 --- a/Multiplatform/Shared/Sidebar/SidebarItem.swift +++ b/Multiplatform/Shared/Sidebar/SidebarItem.swift @@ -77,8 +77,22 @@ struct SidebarItem: Identifiable { self.nameForDisplay = feed.nameForDisplay } + /// Add a sidebar item to the child list mutating func addChild(_ sidebarItem: SidebarItem) { children.append(sidebarItem) } + /// Recursively visits each sidebar item. Return true when done visiting. + @discardableResult + func visit(_ block: (SidebarItem) -> Bool) -> Bool { + let stop = block(self) + if !stop { + for child in children { + if child.visit(block) { + break + } + } + } + return stop + } } diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index b58b65a7d..649313fdb 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -43,14 +43,11 @@ class SidebarModel: ObservableObject, UndoableCommandRunner { // MARK: API func goToNextUnread() { - guard let lastSelectedFeed = selectedFeeds.last, - let currentSidebarItem = sidebarItems.first(where: { $0.feed?.feedID == lastSelectedFeed.feedID }) else { - return - } + guard let startFeed = selectedFeeds.first ?? sidebarItems.first?.children.first?.feed else { return } - if !goToNextUnread(startingAt: currentSidebarItem) { - if let firstSidebarItem = sidebarItems.first { - goToNextUnread(startingAt: firstSidebarItem) + if !goToNextUnread(startingAt: startFeed) { + if let firstFeed = sidebarItems.first?.children.first?.feed { + goToNextUnread(startingAt: firstFeed) } } } @@ -193,15 +190,30 @@ private extension SidebarModel { } @discardableResult - func goToNextUnread(startingAt: SidebarItem) -> Bool { - guard let startIndex = sidebarItems.firstIndex(where: { $0.id == startingAt.id }) else { return false } - - for i in startIndex.. 0, let feedID = sidebarItems[i].feed?.feedID { - select(feedID) - return true + func goToNextUnread(startingAt: Feed) -> Bool { + + var foundStartFeed = false + var nextSidebarItem: SidebarItem? = nil + for section in sidebarItems { + if nextSidebarItem == nil { + section.visit { sidebarItem in + if !foundStartFeed && sidebarItem.feed?.feedID == startingAt.feedID { + foundStartFeed = true + return false + } + if foundStartFeed && sidebarItem.unreadCount > 0 { + nextSidebarItem = sidebarItem + return true + } + return false + } } } + + if let nextFeedID = nextSidebarItem?.feed?.feedID { + select(nextFeedID) + return true + } return false } diff --git a/Multiplatform/Shared/Timeline/TimelineModel.swift b/Multiplatform/Shared/Timeline/TimelineModel.swift index f8bf78e16..e6c079fec 100644 --- a/Multiplatform/Shared/Timeline/TimelineModel.swift +++ b/Multiplatform/Shared/Timeline/TimelineModel.swift @@ -315,12 +315,23 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { openIndicatedArticleInBrowser(article) } - func canGoToNextUnread() -> Bool { - return false - } - - func goToNextUnread() { + @discardableResult + func goToNextUnread() -> Bool { + var startIndex: Int + if let firstArticle = selectedArticles.first, let index = timelineItems.firstIndex(where: { $0.article == firstArticle }) { + startIndex = index + } else { + startIndex = 0 + } + for i in startIndex.. Article? { @@ -382,6 +393,11 @@ private extension TimelineModel { runCommand(markReadCommand) } + func select(_ articleID: String) { + selectedArticleIDs = Set([articleID]) + selectedArticleID = articleID + } + // MARK: Timeline Management func resetReadFilter() {