Reenable Timeline context menus

This commit is contained in:
Maurice Parker 2020-07-26 13:57:50 -05:00
parent 9e1e55ac8d
commit 539685586e
2 changed files with 228 additions and 95 deletions

View File

@ -15,97 +15,95 @@ struct TimelineContextMenu: View {
@ViewBuilder var body: some View { @ViewBuilder var body: some View {
Button("Coming back soon...", action: {}) if timelineModel.canMarkIndicatedArticlesAsRead(timelineItem) {
Button {
// if timelineModel.canMarkIndicatedArticlesAsRead(timelineItem) { timelineModel.markIndicatedArticlesAsRead(timelineItem)
// Button { } label: {
// timelineModel.markIndicatedArticlesAsRead(timelineItem) Text("Mark as Read")
// } label: { #if os(iOS)
// Text("Mark as Read") AppAssets.readOpenImage
// #if os(iOS) #endif
// AppAssets.readOpenImage }
// #endif }
// }
// } if timelineModel.canMarkIndicatedArticlesAsUnread(timelineItem) {
// Button {
// if timelineModel.canMarkIndicatedArticlesAsUnread(timelineItem) { timelineModel.markIndicatedArticlesAsUnread(timelineItem)
// Button { } label: {
// timelineModel.markIndicatedArticlesAsUnread(timelineItem) Text("Mark as Unread")
// } label: { #if os(iOS)
// Text("Mark as Unread") AppAssets.readClosedImage
// #if os(iOS) #endif
// AppAssets.readClosedImage }
// #endif }
// }
// } if timelineModel.canMarkIndicatedArticlesAsStarred(timelineItem) {
// Button {
// if timelineModel.canMarkIndicatedArticlesAsStarred(timelineItem) { timelineModel.markIndicatedArticlesAsStarred(timelineItem)
// Button { } label: {
// timelineModel.markIndicatedArticlesAsStarred(timelineItem) Text("Mark as Starred")
// } label: { #if os(iOS)
// Text("Mark as Starred") AppAssets.starClosedImage
// #if os(iOS) #endif
// AppAssets.starClosedImage }
// #endif }
// }
// } if timelineModel.canMarkIndicatedArticlesAsUnstarred(timelineItem) {
// Button {
// if timelineModel.canMarkIndicatedArticlesAsUnstarred(timelineItem) { timelineModel.markIndicatedArticlesAsUnstarred(timelineItem)
// Button { } label: {
// timelineModel.markIndicatedArticlesAsUnstarred(timelineItem) Text("Mark as Unstarred")
// } label: { #if os(iOS)
// Text("Mark as Unstarred") AppAssets.starOpenImage
// #if os(iOS) #endif
// AppAssets.starOpenImage }
// #endif }
// }
// } if timelineModel.canMarkAboveAsRead(timelineItem) {
// Button {
// if timelineModel.canMarkAboveAsRead(timelineItem) { timelineModel.markAboveAsRead(timelineItem)
// Button { } label: {
// timelineModel.markAboveAsRead(timelineItem) Text("Mark Above as Read")
// } label: { #if os(iOS)
// Text("Mark Above as Read") AppAssets.markAboveAsReadImage
// #if os(iOS) #endif
// AppAssets.markAboveAsReadImage }
// #endif }
// }
// } if timelineModel.canMarkBelowAsRead(timelineItem) {
// Button {
// if timelineModel.canMarkBelowAsRead(timelineItem) { timelineModel.markBelowAsRead(timelineItem)
// Button { } label: {
// timelineModel.markBelowAsRead(timelineItem) Text("Mark Below As Read")
// } label: { #if os(iOS)
// Text("Mark Below As Read") AppAssets.markBelowAsReadImage
// #if os(iOS) #endif
// AppAssets.markBelowAsReadImage }
// #endif }
// }
// } if timelineModel.canMarkAllAsReadInWebFeed(timelineItem) {
// Divider()
// if timelineModel.canMarkAllAsReadInWebFeed(timelineItem) { Button {
// Divider() timelineModel.markAllAsReadInWebFeed(timelineItem)
// Button { } label: {
// timelineModel.markAllAsReadInWebFeed(timelineItem) Text("Mark All as Read in “\(timelineItem.article.webFeed?.nameForDisplay ?? "")")
// } label: { #if os(iOS)
// Text("Mark All as Read in \(timelineItem.article.webFeed?.nameForDisplay ?? "")") AppAssets.markAllAsReadImage
// #if os(iOS) #endif
// AppAssets.markAllAsReadImage }
// #endif }
// }
// } if timelineModel.canOpenIndicatedArticleInBrowser(timelineItem) {
// Divider()
// if timelineModel.canOpenIndicatedArticleInBrowser(timelineItem) { Button {
// Divider() timelineModel.openIndicatedArticleInBrowser(timelineItem)
// Button { } label: {
// timelineModel.openIndicatedArticleInBrowser(timelineItem) Text("Open in Browser")
// } label: { #if os(iOS)
// Text("Open in Browser") AppAssets.openInBrowserImage
// #if os(iOS) #endif
// AppAssets.openInBrowserImage }
// #endif }
// }
// }
} }
} }

View File

@ -29,7 +29,9 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
@Published var selectedTimelineItemIDs = Set<String>() // Don't use directly. Use selectedTimelineItemsPublisher @Published var selectedTimelineItemIDs = Set<String>() // Don't use directly. Use selectedTimelineItemsPublisher
@Published var selectedTimelineItemID: String? = nil // Don't use directly. Use selectedTimelineItemsPublisher @Published var selectedTimelineItemID: String? = nil // Don't use directly. Use selectedTimelineItemsPublisher
var selectedArticles = [Article]() var selectedArticles: [Article] {
return selectedTimelineItems.map { $0.article }
}
var timelineItemsPublisher: AnyPublisher<TimelineItems, Never>? var timelineItemsPublisher: AnyPublisher<TimelineItems, Never>?
var selectedTimelineItemsPublisher: AnyPublisher<[TimelineItem], Never>? var selectedTimelineItemsPublisher: AnyPublisher<[TimelineItem], Never>?
@ -55,7 +57,9 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
private var sortDirectionSubject = ReplaySubject<Bool, Never>(bufferSize: 1) private var sortDirectionSubject = ReplaySubject<Bool, Never>(bufferSize: 1)
private var groupByFeedSubject = ReplaySubject<Bool, Never>(bufferSize: 1) private var groupByFeedSubject = ReplaySubject<Bool, Never>(bufferSize: 1)
private var selectedTimelineItems = [TimelineItem]()
private var timelineItems = TimelineItems() private var timelineItems = TimelineItems()
private var articles = [Article]()
init(delegate: TimelineModelDelegate) { init(delegate: TimelineModelDelegate) {
self.delegate = delegate self.delegate = delegate
@ -68,6 +72,8 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
subscribeToOpenInBrowserEvents() subscribeToOpenInBrowserEvents()
} }
// MARK: API
@discardableResult @discardableResult
func goToNextUnread() -> Bool { func goToNextUnread() -> Bool {
// var startIndex: Int // var startIndex: Int
@ -108,7 +114,100 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
func selectArticle(_ article: Article) { func selectArticle(_ article: Article) {
// TODO: Implement me! // TODO: Implement me!
} }
func canMarkIndicatedArticlesAsRead(_ timelineItem: TimelineItem) -> Bool {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
return articles.anyArticleIsUnread()
}
func markIndicatedArticlesAsRead(_ timelineItem: TimelineItem) {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
markArticlesWithUndo(articles, statusKey: .read, flag: true)
}
func canMarkIndicatedArticlesAsUnread(_ timelineItem: TimelineItem) -> Bool {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
return articles.anyArticleIsReadAndCanMarkUnread()
}
func markIndicatedArticlesAsUnread(_ timelineItem: TimelineItem) {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
markArticlesWithUndo(articles, statusKey: .read, flag: false)
}
func canMarkAboveAsRead(_ timelineItem: TimelineItem) -> Bool {
let timelineItem = indicatedAboveTimelineItem(timelineItem)
return articles.articlesAbove(position: timelineItem.position).canMarkAllAsRead()
}
func markAboveAsRead(_ timelineItem: TimelineItem) {
let timelineItem = indicatedAboveTimelineItem(timelineItem)
let articlesToMark = articles.articlesAbove(position: timelineItem.position)
guard !articlesToMark.isEmpty else { return }
markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true)
}
func canMarkBelowAsRead(_ timelineItem: TimelineItem) -> Bool {
let timelineItem = indicatedBelowTimelineItem(timelineItem)
return articles.articlesBelow(position: timelineItem.position).canMarkAllAsRead()
}
func markBelowAsRead(_ timelineItem: TimelineItem) {
let timelineItem = indicatedBelowTimelineItem(timelineItem)
let articlesToMark = articles.articlesBelow(position: timelineItem.position)
guard !articlesToMark.isEmpty else { return }
markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true)
}
func canMarkAllAsReadInWebFeed(_ timelineItem: TimelineItem) -> Bool {
return timelineItem.article.webFeed?.unreadCount ?? 0 > 0
}
func markAllAsReadInWebFeed(_ timelineItem: TimelineItem) {
guard let articlesSet = try? timelineItem.article.webFeed?.fetchArticles() else { return }
let articlesToMark = Array(articlesSet)
markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true)
}
func canMarkIndicatedArticlesAsStarred(_ timelineItem: TimelineItem) -> Bool {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
return articles.anyArticleIsUnstarred()
}
func markIndicatedArticlesAsStarred(_ timelineItem: TimelineItem) {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
markArticlesWithUndo(articles, statusKey: .starred, flag: true)
}
func canMarkIndicatedArticlesAsUnstarred(_ timelineItem: TimelineItem) -> Bool {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
return articles.anyArticleIsStarred()
}
func markIndicatedArticlesAsUnstarred(_ timelineItem: TimelineItem) {
let articles = indicatedTimelineItems(timelineItem).map { $0.article }
markArticlesWithUndo(articles, statusKey: .starred, flag: false)
}
func canOpenIndicatedArticleInBrowser(_ timelineItem: TimelineItem) -> Bool {
guard indicatedTimelineItems(timelineItem).count == 1 else { return false }
return timelineItem.article.preferredLink != nil
}
func openIndicatedArticleInBrowser(_ timelineItem: TimelineItem) {
openIndicatedArticleInBrowser(timelineItem.article)
}
func openIndicatedArticleInBrowser(_ article: Article) {
guard let link = article.preferredLink else { return }
#if os(macOS)
Browser.open(link, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false)
#else
guard let url = URL(string: link) else { return }
UIApplication.shared.open(url, options: [:])
#endif
}
} }
// MARK: Private // MARK: Private
@ -255,6 +354,7 @@ private extension TimelineModel {
timelineItemsPublisher! timelineItemsPublisher!
.sink { [weak self] timelineItems in .sink { [weak self] timelineItems in
self?.timelineItems = timelineItems self?.timelineItems = timelineItems
self?.articles = timelineItems.items.map { $0.article }
} }
.store(in: &cancellables) .store(in: &cancellables)
@ -297,9 +397,9 @@ private extension TimelineModel {
.share(replay: 1) .share(replay: 1)
.eraseToAnyPublisher() .eraseToAnyPublisher()
selectedArticlesPublisher! selectedTimelineItemsPublisher!
.sink { [weak self] selectedArticles in .sink { [weak self] selectedTimelineItems in
self?.selectedArticles = selectedArticles self?.selectedTimelineItems = selectedTimelineItems
} }
.store(in: &cancellables) .store(in: &cancellables)
@ -446,4 +546,39 @@ private extension TimelineModel {
} }
return false return false
} }
// MARK: Aricle Marking
func indicatedTimelineItems(_ timelineItem: TimelineItem) -> [TimelineItem] {
if selectedTimelineItems.contains(where: { $0.id == timelineItem.id }) {
return selectedTimelineItems
} else {
return [timelineItem]
}
}
func indicatedAboveTimelineItem(_ timelineItem: TimelineItem) -> TimelineItem {
if selectedTimelineItems.contains(where: { $0.id == timelineItem.id }) {
return selectedTimelineItems.sorted(by: { $0.position < $1.position }).first!
} else {
return timelineItem
}
}
func indicatedBelowTimelineItem(_ timelineItem: TimelineItem) -> TimelineItem {
if selectedTimelineItems.contains(where: { $0.id == timelineItem.id }) {
return selectedTimelineItems.sorted(by: { $0.position < $1.position }).last!
} else {
return timelineItem
}
}
func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) {
if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) {
runCommand(markReadCommand)
} else {
markArticles(Set(articles), statusKey: statusKey, flag: flag)
}
}
} }