Fix build errors.

This commit is contained in:
Brent Simmons 2024-03-19 10:15:30 -07:00
parent 5c6e5807d9
commit b2d3128b2d
11 changed files with 256 additions and 184 deletions

View File

@ -456,7 +456,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func suspendDatabase() {
#if os(iOS)
database.cancelAndSuspend()
Task {
await database.suspend()
}
#endif
save()
}
@ -465,7 +467,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
/// Call this *before* calling resume.
public func resumeDatabaseAndDelegate() {
#if os(iOS)
database.resume()
Task {
await database.resume()
}
#endif
delegate.resume()
}

View File

@ -282,7 +282,7 @@ public actor ArticlesDatabase {
#endif
}
func resume() {
public func resume() {
#if os(iOS)
if database == nil {
self.database = FMDatabase.openAndSetUpDatabase(path: databasePath)

View File

@ -968,28 +968,6 @@ extension AppDelegate: NSWindowRestoration {
private extension AppDelegate {
struct ArticlePathInfo {
let accountID: String
let articleID: String
init?(userInfo: [AnyHashable: Any]) {
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [String: String] else {
return nil
}
guard let accountID = articlePathUserInfo[ArticlePathKey.accountID] else {
return nil
}
guard let articleID = articlePathUserInfo[ArticlePathKey.articleID] else {
return nil
}
self.accountID = accountID
self.articleID = articleID
}
}
func handleMarkAsRead(userInfo: [AnyHashable: Any]) {
guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {

View File

@ -632,6 +632,9 @@
842E45DD1ED8C54B000A8B52 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45DC1ED8C54B000A8B52 /* Browser.swift */; };
84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; };
8444C8F21FED81840051386C /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; };
844933D22BA953590068AC51 /* ArticlePathInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844933D12BA953590068AC51 /* ArticlePathInfo.swift */; };
844933D32BA953590068AC51 /* ArticlePathInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844933D12BA953590068AC51 /* ArticlePathInfo.swift */; };
844933D42BA953590068AC51 /* ArticlePathInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844933D12BA953590068AC51 /* ArticlePathInfo.swift */; };
844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */; };
844B5B5B1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B5A1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift */; };
844B5B651FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */; };
@ -1341,6 +1344,7 @@
842E45DC1ED8C54B000A8B52 /* Browser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Browser.swift; sourceTree = "<group>"; };
84411E701FE5FBFA004B527F /* SmallIconProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallIconProvider.swift; sourceTree = "<group>"; };
8444C8F11FED81840051386C /* OPMLExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLExporter.swift; sourceTree = "<group>"; };
844933D12BA953590068AC51 /* ArticlePathInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticlePathInfo.swift; sourceTree = "<group>"; };
844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarKeyboardDelegate.swift; sourceTree = "<group>"; };
844B5B5A1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineKeyboardDelegate.swift; sourceTree = "<group>"; };
844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = GlobalKeyboardShortcuts.plist; sourceTree = "<group>"; };
@ -2445,6 +2449,7 @@
842E45CD1ED8C308000A8B52 /* AppNotifications.swift */,
51C4CFEF24D37D1F00AF9874 /* Secrets.swift */,
511B9805237DCAC90028BCAA /* UserInfoKey.swift */,
844933D12BA953590068AC51 /* ArticlePathInfo.swift */,
8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */,
845122752B8CEA9B00480DB0 /* SidebarItem */,
51C452AD2265102800C03939 /* Timeline */,
@ -3802,6 +3807,7 @@
65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */,
65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */,
65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */,
844933D32BA953590068AC51 /* ArticlePathInfo.swift in Sources */,
51386A8F25673277005F3762 /* AccountCell.swift in Sources */,
65ED3FFE235DEF6C0081F399 /* AccountsFeedbinWindowController.swift in Sources */,
65ED3FFF235DEF6C0081F399 /* SidebarOutlineDataSource.swift in Sources */,
@ -4039,6 +4045,7 @@
B24E9ADE245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */,
C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */,
17D7586F2679C21800B17787 /* OnePasswordExtension.m in Sources */,
844933D42BA953590068AC51 /* ArticlePathInfo.swift in Sources */,
17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */,
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
@ -4139,6 +4146,7 @@
51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */,
849A978A1ED9ECEF007D329B /* ArticleThemesManager.swift in Sources */,
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
844933D22BA953590068AC51 /* ArticlePathInfo.swift in Sources */,
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */,
FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */,
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,

View File

@ -7,7 +7,6 @@
//
import Foundation
import CoreSpotlight
import CoreServices
import RSCore
import Account
@ -15,6 +14,12 @@ import Articles
import Intents
import UniformTypeIdentifiers
#if os(iOS)
@preconcurrency import CoreSpotlight
#else
import CoreSpotlight
#endif
class ActivityManager {
private var nextUnreadActivity: NSUserActivity?
@ -109,34 +114,45 @@ class ActivityManager {
#if os(iOS)
static func cleanUp(_ account: Account) {
var ids = [String]()
if let folders = account.folders {
for folder in folders {
ids.append(identifier(for: folder))
Task { @MainActor in
var ids = [String]()
if let folders = account.folders {
for folder in folders {
ids.append(identifier(for: folder))
}
}
for feed in account.flattenedFeeds() {
let feedIdentifiers = await identifiers(for: feed)
ids.append(contentsOf: feedIdentifiers)
}
try? await CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
for feed in account.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: feed))
}
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
static func cleanUp(_ folder: Folder) {
var ids = [String]()
ids.append(identifier(for: folder))
for feed in folder.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: feed))
Task { @MainActor in
var ids = [String]()
ids.append(identifier(for: folder))
for feed in folder.flattenedFeeds() {
let feedIdentifiers = await identifiers(for: feed)
ids.append(contentsOf: feedIdentifiers)
}
try? await CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
static func cleanUp(_ feed: Feed) {
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifiers(for: feed))
Task { @MainActor in
let feedIdentifiers = await identifiers(for: feed)
try? await CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: feedIdentifiers)
}
}
#endif

View File

@ -0,0 +1,31 @@
//
// ArticlePathInfo.swift
// NetNewsWire
//
// Created by Brent Simmons on 3/18/24.
// Copyright © 2024 Ranchero Software. All rights reserved.
//
import Foundation
struct ArticlePathInfo {
let accountID: String
let articleID: String
init?(userInfo: [AnyHashable: Any]) {
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [String: String] else {
return nil
}
guard let accountID = articlePathUserInfo[ArticlePathKey.accountID] else {
return nil
}
guard let articleID = articlePathUserInfo[ArticlePathKey.articleID] else {
return nil
}
self.accountID = accountID
self.articleID = articleID
}
}

View File

@ -31,81 +31,81 @@ public final class WidgetDataEncoder {
func encodeWidgetData() throws {
os_log(.debug, log: log, "Starting encoding widget data.")
do {
let unreadArticles = Array(try AccountManager.shared.fetchArticles(.unread(fetchLimit))).sortedByDate(.orderedDescending)
let starredArticles = Array(try AccountManager.shared.fetchArticles(.starred(fetchLimit))).sortedByDate(.orderedDescending)
let todayArticles = Array(try AccountManager.shared.fetchArticles(.today(fetchLimit))).sortedByDate(.orderedDescending)
var unread = [LatestArticle]()
var today = [LatestArticle]()
var starred = [LatestArticle]()
for article in unreadArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished?.description ?? "")
unread.append(latestArticle)
}
for article in starredArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished?.description ?? "")
starred.append(latestArticle)
}
for article in todayArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished?.description ?? "")
today.append(latestArticle)
}
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count,
unreadArticles: unread,
starredArticles: starred,
todayArticles:today,
lastUpdateTime: Date())
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "com.ranchero.NetNewsWire.Encode") {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
}
let encodedData = try? JSONEncoder().encode(latestData)
os_log(.debug, log: self.log, "Finished encoding widget data.")
if self.fileExists() {
try? FileManager.default.removeItem(at: self.dataURL!)
os_log(.debug, log: self.log, "Removed widget data from container.")
}
if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
os_log(.debug, log: self.log, "Wrote widget data to container.")
WidgetCenter.shared.reloadAllTimelines()
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
} else {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
}
}
}
// do {
// let unreadArticles = Array(try AccountManager.shared.fetchArticles(fetchType: .unread(fetchLimit))).sortedByDate(.orderedDescending)
// let starredArticles = Array(try AccountManager.shared.fetchArticles(fetchType: .starred(fetchLimit))).sortedByDate(.orderedDescending)
// let todayArticles = Array(try AccountManager.shared.fetchArticles(fetchType: .today(fetchLimit))).sortedByDate(.orderedDescending)
//
// var unread = [LatestArticle]()
// var today = [LatestArticle]()
// var starred = [LatestArticle]()
//
// for article in unreadArticles {
// let latestArticle = LatestArticle(id: article.sortableArticleID,
// feedTitle: article.sortableName,
// articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
// articleSummary: article.summary,
// feedIcon: article.iconImage()?.image.dataRepresentation(),
// pubDate: article.datePublished?.description ?? "")
// unread.append(latestArticle)
// }
//
// for article in starredArticles {
// let latestArticle = LatestArticle(id: article.sortableArticleID,
// feedTitle: article.sortableName,
// articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
// articleSummary: article.summary,
// feedIcon: article.iconImage()?.image.dataRepresentation(),
// pubDate: article.datePublished?.description ?? "")
// starred.append(latestArticle)
// }
//
// for article in todayArticles {
// let latestArticle = LatestArticle(id: article.sortableArticleID,
// feedTitle: article.sortableName,
// articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
// articleSummary: article.summary,
// feedIcon: article.iconImage()?.image.dataRepresentation(),
// pubDate: article.datePublished?.description ?? "")
// today.append(latestArticle)
// }
//
// let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
// currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
// currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count,
// unreadArticles: unread,
// starredArticles: starred,
// todayArticles:today,
// lastUpdateTime: Date())
//
//
// DispatchQueue.global().async { [weak self] in
// guard let self = self else { return }
//
// self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "com.ranchero.NetNewsWire.Encode") {
// UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
// self.backgroundTaskID = .invalid
// }
// let encodedData = try? JSONEncoder().encode(latestData)
//
// os_log(.debug, log: self.log, "Finished encoding widget data.")
//
// if self.fileExists() {
// try? FileManager.default.removeItem(at: self.dataURL!)
// os_log(.debug, log: self.log, "Removed widget data from container.")
// }
// if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
// os_log(.debug, log: self.log, "Wrote widget data to container.")
// WidgetCenter.shared.reloadAllTimelines()
// UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
// self.backgroundTaskID = .invalid
// } else {
// UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
// self.backgroundTaskID = .invalid
// }
//
// }
// }
}
private func fileExists() -> Bool {

View File

@ -421,58 +421,70 @@ private extension AppDelegate {
private extension AppDelegate {
func handleMarkAsRead(userInfo: [AnyHashable: Any]) {
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
return
@MainActor func handleMarkAsRead(userInfo: [AnyHashable: Any]) {
guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {
return
}
resumeDatabaseProcessingIfNecessary()
let account = AccountManager.shared.existingAccount(with: accountID)
guard account != nil else {
guard let account = AccountManager.shared.existingAccount(with: articlePathInfo.accountID) else {
os_log(.debug, "No account found from notification.")
return
}
let article = try? account!.fetchArticles(.articleIDs([articleID]))
guard article != nil else {
os_log(.debug, "No article found from search using %@", articleID)
return
}
account!.markArticles(article!, statusKey: .read, flag: true) { _ in }
self.prepareAccountsForBackground()
account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self?.prepareAccountsForBackground()
self?.suspendApplication()
let articleID = articlePathInfo.articleID
Task { @MainActor in
guard let articles = try? await account.articles(for: .articleIDs([articleID])) else {
os_log(.debug, "No article found from search using %@", articleID)
return
}
})
account.markArticles(articles, statusKey: .read, flag: true) { _ in }
self.prepareAccountsForBackground()
account.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self?.prepareAccountsForBackground()
self?.suspendApplication()
}
})
}
}
func handleMarkAsStarred(userInfo: [AnyHashable: Any]) {
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
return
@MainActor func handleMarkAsStarred(userInfo: [AnyHashable: Any]) {
guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {
return
}
resumeDatabaseProcessingIfNecessary()
let account = AccountManager.shared.existingAccount(with: accountID)
guard account != nil else {
guard let account = AccountManager.shared.existingAccount(with: articlePathInfo.accountID) else {
os_log(.debug, "No account found from notification.")
return
}
let article = try? account!.fetchArticles(.articleIDs([articleID]))
guard article != nil else {
os_log(.debug, "No article found from search using %@", articleID)
return
}
account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self?.prepareAccountsForBackground()
self?.suspendApplication()
let articleID = articlePathInfo.articleID
Task { @MainActor in
guard let articles = try? await account.articles(for: .articleIDs([articleID])) else {
os_log(.debug, "No article found from search using %@", articleID)
return
}
})
account.markArticles(articles, statusKey: .starred, flag: true) { _ in }
account.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData()
self?.prepareAccountsForBackground()
self?.suspendApplication()
}
})
}
}
}

View File

@ -986,6 +986,7 @@ private extension SidebarViewController {
}
func copyHomePageAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let homePageURL = feed.homePageURL,
let url = URL(string: homePageURL) else {
@ -1001,9 +1002,12 @@ private extension SidebarViewController {
}
func markAllAsReadAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
feed.unreadCount > 0,
let articles = try? feed.fetchArticles(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
feed.unreadCount > 0 else {
return nil
}
guard let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
return nil
}
@ -1016,8 +1020,13 @@ private extension SidebarViewController {
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
self?.coordinator.markAllAsRead(Array(articles))
completion(true)
Task { @MainActor in
if let articles = try? await feed.fetchUnreadArticles() {
self?.coordinator.markAllAsRead(Array(articles))
}
completion(true)
}
}
}
return action
@ -1092,8 +1101,11 @@ private extension SidebarViewController {
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
if let articles = try? feed.fetchUnreadArticles() {
self?.coordinator.markAllAsRead(Array(articles))
Task { @MainActor in
if let articles = try? await feed.fetchUnreadArticles() {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
@ -1112,8 +1124,11 @@ private extension SidebarViewController {
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let articles = try? account.fetchArticles(.unread()) {
self?.coordinator.markAllAsRead(Array(articles))
Task { @MainActor in
if let articles = try? await account.articles(for: .unread()) {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}

View File

@ -51,8 +51,8 @@ struct SidebarItemNode: Hashable {
}
}
class SceneCoordinator: NSObject, UndoableCommandRunner {
@MainActor final class SceneCoordinator: NSObject, UndoableCommandRunner {
var undoableCommands = [UndoableCommand]()
var undoManager: UndoManager? {
return rootSplitViewController.undoManager

View File

@ -867,36 +867,37 @@ private extension TimelineViewController {
}
func markAllInFeedAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
guard let feed = article.feed else { return nil }
guard let fetchedArticles = try? feed.fetchArticles() else {
return nil
}
let articles = Array(fetchedArticles)
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
guard let feed = article.feed, feed.unreadCount > 0 else {
return nil
}
guard let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
return nil
}
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
self?.coordinator.markAllAsRead(articles)
Task { @MainActor in
if let articles = try? await feed.fetchArticles() {
self?.coordinator.markAllAsRead(Array(articles))
}
}
}
}
return action
}
func markAllInFeedAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let feed = article.feed else { return nil }
guard let fetchedArticles = try? feed.fetchArticles() else {
guard let feed = article.feed, feed.unreadCount > 0 else {
return nil
}
let articles = Array(fetchedArticles)
guard articles.canMarkAllAsRead(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
guard let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
return nil
}
@ -908,8 +909,15 @@ private extension TimelineViewController {
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
self?.coordinator.markAllAsRead(articles)
completion(true)
Task { @MainActor in
if let articles = try? await feed.fetchArticles() {
self?.coordinator.markAllAsRead(Array(articles))
completion(true)
} else {
completion(false)
}
}
}
}
return action