Reenable Timeline context menus
This commit is contained in:
parent
9e1e55ac8d
commit
539685586e
@ -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
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user