diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index 09a9e1b6b..683ecb1e7 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -7,6 +7,7 @@ // import Foundation +import Combine import RSCore import Account @@ -19,31 +20,13 @@ class SidebarModel: ObservableObject { weak var delegate: SidebarModelDelegate? @Published var sidebarItems = [SidebarItem]() + @Published var selectedFeedIdentifiers = Set() + @Published var selectedFeedIdentifier: FeedIdentifier? = .none + @Published var selectedFeeds = [Feed]() - #if os(macOS) - @Published var selectedSidebarItems = Set() { - didSet { - print(selectedSidebarItems) - } - } - #endif - - private var items = Set() - - @Published var selectedSidebarItem: FeedIdentifier? = .none { - willSet { - #if os(macOS) - if newValue != nil { - items.insert(newValue!) - } else { - selectedSidebarItems = items - items.removeAll() - } - #endif - } - } - - + private var selectedFeedIdentifiersCancellable: AnyCancellable? + private var selectedFeedIdentifierCancellable: AnyCancellable? + init() { NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidInitialize(_:)), name: .UnreadCountDidInitialize, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil) @@ -52,6 +35,21 @@ class SidebarModel: ObservableObject { NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange(_:)), name: .AccountStateDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidAddAccount(_:)), name: .UserDidAddAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidDeleteAccount(_:)), name: .UserDidDeleteAccount, object: nil) + + // TODO: This should be rewritten to use Combine correctly + selectedFeedIdentifiersCancellable = $selectedFeedIdentifiers.sink { [weak self] feedIDs in + guard let self = self else { return } + self.selectedFeeds = feedIDs.compactMap { AccountManager.shared.existingFeed(with: $0) } + } + + // TODO: This should be rewritten to use Combine correctly + selectedFeedIdentifierCancellable = $selectedFeedIdentifier.sink { [weak self] feedID in + guard let self = self else { return } + if let feedID = feedID, let feed = AccountManager.shared.existingFeed(with: feedID) { + self.selectedFeeds = [feed] + } + } + } // MARK: API @@ -86,6 +84,7 @@ class SidebarModel: ObservableObject { sidebarItems = items } + } // MARK: Private diff --git a/Multiplatform/Shared/Sidebar/SidebarView.swift b/Multiplatform/Shared/Sidebar/SidebarView.swift index cd9a7e920..d5eb28754 100644 --- a/Multiplatform/Shared/Sidebar/SidebarView.swift +++ b/Multiplatform/Shared/Sidebar/SidebarView.swift @@ -15,27 +15,36 @@ struct SidebarView: View { // @SceneStorage("expandedContainers") private var expandedContainerData = Data() @StateObject private var expandedContainers = SidebarExpandedContainers() @EnvironmentObject private var sidebarModel: SidebarModel - + @State var navigate = false + @ViewBuilder var body: some View { #if os(macOS) - List(selection: $sidebarModel.selectedSidebarItems) { - containedList + ZStack { + NavigationLink(destination: TimelineContainerView(feeds: sidebarModel.selectedFeeds), isActive: $navigate) { + EmptyView() + }.hidden() + List(selection: $sidebarModel.selectedFeedIdentifiers) { + rows + } + } + .onChange(of: sidebarModel.selectedFeedIdentifiers) { value in + navigate = !sidebarModel.selectedFeedIdentifiers.isEmpty } #else List { - containedList + rows } #endif - // .onAppear { - // expandedContainers.data = expandedContainerData - // } - // .onReceive(expandedContainers.objectDidChange) { - // expandedContainerData = expandedContainers.data - // } +// .onAppear { +// expandedContainers.data = expandedContainerData +// } +// .onReceive(expandedContainers.objectDidChange) { +// expandedContainerData = expandedContainers.data +// } } - var containedList: some View { + var rows: some View { ForEach(sidebarModel.sidebarItems) { sidebarItem in if let containerID = sidebarItem.containerID { DisclosureGroup(isExpanded: $expandedContainers[containerID]) { @@ -43,34 +52,13 @@ struct SidebarView: View { if let containerID = sidebarItem.containerID { DisclosureGroup(isExpanded: $expandedContainers[containerID]) { ForEach(sidebarItem.children) { sidebarItem in - ZStack { - SidebarItemView(sidebarItem: sidebarItem) - NavigationLink(destination: (TimelineContainerView(feed: sidebarItem.feed)), - tag: sidebarItem.feed!.feedID!, - selection: $sidebarModel.selectedSidebarItem) { - EmptyView() - }.buttonStyle(PlainButtonStyle()) - } + buildSidebarItemNavigation(sidebarItem) } } label: { - ZStack { - SidebarItemView(sidebarItem: sidebarItem) - NavigationLink(destination: (TimelineContainerView(feed: sidebarItem.feed)), - tag: sidebarItem.feed!.feedID!, - selection: $sidebarModel.selectedSidebarItem) { - EmptyView() - }.buttonStyle(PlainButtonStyle()) - } + buildSidebarItemNavigation(sidebarItem) } } else { - ZStack { - SidebarItemView(sidebarItem: sidebarItem) - NavigationLink(destination: (TimelineContainerView(feed: sidebarItem.feed)), - tag: sidebarItem.feed!.feedID!, - selection: $sidebarModel.selectedSidebarItem) { - EmptyView() - }.buttonStyle(PlainButtonStyle()) - } + buildSidebarItemNavigation(sidebarItem) } } } label: { @@ -80,4 +68,19 @@ struct SidebarView: View { } } + func buildSidebarItemNavigation(_ sidebarItem: SidebarItem) -> some View { + #if os(macOS) + return SidebarItemView(sidebarItem: sidebarItem).tag(sidebarItem.feed!.feedID!) + #else + return ZStack { + SidebarItemView(sidebarItem: sidebarItem) + NavigationLink(destination: TimelineContainerView(feeds: sidebarModel.selectedFeeds), + tag: sidebarItem.feed!.feedID!, + selection: $sidebarModel.selectedFeedIdentifier) { + EmptyView() + }.buttonStyle(PlainButtonStyle()) + } + #endif + } + } diff --git a/Multiplatform/Shared/Timeline/TimelineContainerView.swift b/Multiplatform/Shared/Timeline/TimelineContainerView.swift index 8329e14d7..690d1e0fd 100644 --- a/Multiplatform/Shared/Timeline/TimelineContainerView.swift +++ b/Multiplatform/Shared/Timeline/TimelineContainerView.swift @@ -13,18 +13,18 @@ struct TimelineContainerView: View { @EnvironmentObject private var sceneModel: SceneModel @StateObject private var timelineModel = TimelineModel() - var feed: Feed? = nil + var feeds: [Feed]? = nil @ViewBuilder var body: some View { - if let feed = feed { + if let feeds = feeds { TimelineView() - .modifier(TimelineTitleModifier(title: feed.nameForDisplay)) + .modifier(TimelineTitleModifier(title: timelineModel.nameForDisplay)) .modifier(TimelineToolbarModifier()) .environmentObject(timelineModel) .onAppear { sceneModel.timelineModel = timelineModel timelineModel.delegate = sceneModel - timelineModel.rebuildTimelineItems(feed) + timelineModel.rebuildTimelineItems(feeds: feeds) } } else { EmptyView() diff --git a/Multiplatform/Shared/Timeline/TimelineModel.swift b/Multiplatform/Shared/Timeline/TimelineModel.swift index 7bedc98e2..c941b6c80 100644 --- a/Multiplatform/Shared/Timeline/TimelineModel.swift +++ b/Multiplatform/Shared/Timeline/TimelineModel.swift @@ -19,9 +19,9 @@ class TimelineModel: ObservableObject { weak var delegate: TimelineModelDelegate? + @Published var nameForDisplay = "" @Published var timelineItems = [TimelineItem]() - private var feeds = [Feed]() private var fetchSerialNumber = 0 private let fetchRequestQueue = FetchRequestQueue() private var exceptionArticleFetcher: ArticleFetcher? @@ -64,9 +64,13 @@ class TimelineModel: ObservableObject { // MARK: API - func rebuildTimelineItems(_ feed: Feed) { - feeds = [feed] - fetchAndReplaceArticlesAsync() + func rebuildTimelineItems(feeds: [Feed]) { + if feeds.count == 1 { + nameForDisplay = feeds.first!.nameForDisplay + } else { + nameForDisplay = NSLocalizedString("Multiple", comment: "Multiple Feeds") + } + fetchAndReplaceArticlesAsync(feeds: feeds) } // TODO: Replace this with ScrollViewReader if we have to keep it @@ -144,9 +148,7 @@ private extension TimelineModel { // MARK: Article Fetching - func fetchAndReplaceArticlesAsync() { - cancelPendingAsyncFetches() - + func fetchAndReplaceArticlesAsync(feeds: [Feed]) { var fetchers = feeds as [ArticleFetcher] if let fetcher = exceptionArticleFetcher { fetchers.append(fetcher) @@ -168,7 +170,7 @@ private extension TimelineModel { // if it’s been superseded by a newer fetch, or the timeline was emptied, etc., it won’t get called. precondition(Thread.isMainThread) cancelPendingAsyncFetches() - let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadFiltered ?? true, representedObjects: representedObjects) { [weak self] (articles, operation) in + let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadFiltered, representedObjects: representedObjects) { [weak self] (articles, operation) in precondition(Thread.isMainThread) guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else { return