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

View File

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

View File

@ -968,28 +968,6 @@ extension AppDelegate: NSWindowRestoration {
private extension AppDelegate { 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]) { func handleMarkAsRead(userInfo: [AnyHashable: Any]) {
guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else { guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {

View File

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

View File

@ -7,7 +7,6 @@
// //
import Foundation import Foundation
import CoreSpotlight
import CoreServices import CoreServices
import RSCore import RSCore
import Account import Account
@ -15,6 +14,12 @@ import Articles
import Intents import Intents
import UniformTypeIdentifiers import UniformTypeIdentifiers
#if os(iOS)
@preconcurrency import CoreSpotlight
#else
import CoreSpotlight
#endif
class ActivityManager { class ActivityManager {
private var nextUnreadActivity: NSUserActivity? private var nextUnreadActivity: NSUserActivity?
@ -109,6 +114,8 @@ class ActivityManager {
#if os(iOS) #if os(iOS)
static func cleanUp(_ account: Account) { static func cleanUp(_ account: Account) {
Task { @MainActor in
var ids = [String]() var ids = [String]()
if let folders = account.folders { if let folders = account.folders {
@ -118,25 +125,34 @@ class ActivityManager {
} }
for feed in account.flattenedFeeds() { for feed in account.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: feed)) let feedIdentifiers = await identifiers(for: feed)
ids.append(contentsOf: feedIdentifiers)
} }
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids) try? await CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
} }
static func cleanUp(_ folder: Folder) { static func cleanUp(_ folder: Folder) {
Task { @MainActor in
var ids = [String]() var ids = [String]()
ids.append(identifier(for: folder)) ids.append(identifier(for: folder))
for feed in folder.flattenedFeeds() { for feed in folder.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: feed)) let feedIdentifiers = await identifiers(for: feed)
ids.append(contentsOf: feedIdentifiers)
} }
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids) try? await CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
} }
static func cleanUp(_ feed: Feed) { 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 #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 { func encodeWidgetData() throws {
os_log(.debug, log: log, "Starting encoding widget data.") os_log(.debug, log: log, "Starting encoding widget data.")
do { // do {
let unreadArticles = Array(try AccountManager.shared.fetchArticles(.unread(fetchLimit))).sortedByDate(.orderedDescending) // let unreadArticles = Array(try AccountManager.shared.fetchArticles(fetchType: .unread(fetchLimit))).sortedByDate(.orderedDescending)
let starredArticles = Array(try AccountManager.shared.fetchArticles(.starred(fetchLimit))).sortedByDate(.orderedDescending) // let starredArticles = Array(try AccountManager.shared.fetchArticles(fetchType: .starred(fetchLimit))).sortedByDate(.orderedDescending)
let todayArticles = Array(try AccountManager.shared.fetchArticles(.today(fetchLimit))).sortedByDate(.orderedDescending) // let todayArticles = Array(try AccountManager.shared.fetchArticles(fetchType: .today(fetchLimit))).sortedByDate(.orderedDescending)
//
var unread = [LatestArticle]() // var unread = [LatestArticle]()
var today = [LatestArticle]() // var today = [LatestArticle]()
var starred = [LatestArticle]() // var starred = [LatestArticle]()
//
for article in unreadArticles { // for article in unreadArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID, // let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName, // feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article), // articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary, // articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(), // feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished?.description ?? "") // pubDate: article.datePublished?.description ?? "")
unread.append(latestArticle) // unread.append(latestArticle)
} // }
//
for article in starredArticles { // for article in starredArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID, // let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName, // feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article), // articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary, // articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(), // feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished?.description ?? "") // pubDate: article.datePublished?.description ?? "")
starred.append(latestArticle) // starred.append(latestArticle)
} // }
//
for article in todayArticles { // for article in todayArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID, // let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName, // feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article), // articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary, // articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(), // feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished?.description ?? "") // pubDate: article.datePublished?.description ?? "")
today.append(latestArticle) // today.append(latestArticle)
} // }
//
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount, // let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount, // currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count, // currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count,
unreadArticles: unread, // unreadArticles: unread,
starredArticles: starred, // starredArticles: starred,
todayArticles:today, // todayArticles:today,
lastUpdateTime: Date()) // lastUpdateTime: Date())
//
//
DispatchQueue.global().async { [weak self] in // DispatchQueue.global().async { [weak self] in
guard let self = self else { return } // guard let self = self else { return }
//
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "com.ranchero.NetNewsWire.Encode") { // self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "com.ranchero.NetNewsWire.Encode") {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!) // UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid // self.backgroundTaskID = .invalid
} // }
let encodedData = try? JSONEncoder().encode(latestData) // let encodedData = try? JSONEncoder().encode(latestData)
//
os_log(.debug, log: self.log, "Finished encoding widget data.") // os_log(.debug, log: self.log, "Finished encoding widget data.")
//
if self.fileExists() { // if self.fileExists() {
try? FileManager.default.removeItem(at: self.dataURL!) // try? FileManager.default.removeItem(at: self.dataURL!)
os_log(.debug, log: self.log, "Removed widget data from container.") // os_log(.debug, log: self.log, "Removed widget data from container.")
} // }
if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) { // if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
os_log(.debug, log: self.log, "Wrote widget data to container.") // os_log(.debug, log: self.log, "Wrote widget data to container.")
WidgetCenter.shared.reloadAllTimelines() // WidgetCenter.shared.reloadAllTimelines()
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!) // UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid // self.backgroundTaskID = .invalid
} else { // } else {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!) // UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid // self.backgroundTaskID = .invalid
} // }
//
} // }
} // }
} }
private func fileExists() -> Bool { private func fileExists() -> Bool {

View File

@ -421,53 +421,31 @@ private extension AppDelegate {
private extension AppDelegate { private extension AppDelegate {
func handleMarkAsRead(userInfo: [AnyHashable: Any]) { @MainActor func handleMarkAsRead(userInfo: [AnyHashable: Any]) {
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String, guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
return return
} }
resumeDatabaseProcessingIfNecessary()
let account = AccountManager.shared.existingAccount(with: accountID)
guard account != nil 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()
}
})
}
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
}
resumeDatabaseProcessingIfNecessary() 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.") os_log(.debug, "No account found from notification.")
return return
} }
let article = try? account!.fetchArticles(.articleIDs([articleID])) let articleID = articlePathInfo.articleID
guard article != nil else {
Task { @MainActor in
guard let articles = try? await account.articles(for: .articleIDs([articleID])) else {
os_log(.debug, "No article found from search using %@", articleID) os_log(.debug, "No article found from search using %@", articleID)
return return
} }
account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
account!.syncArticleStatus(completion: { [weak self] _ in account.markArticles(articles, statusKey: .read, flag: true) { _ in }
self.prepareAccountsForBackground()
account.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended { if !AccountManager.shared.isSuspended {
try? WidgetDataEncoder.shared.encodeWidgetData() try? WidgetDataEncoder.shared.encodeWidgetData()
self?.prepareAccountsForBackground() self?.prepareAccountsForBackground()
@ -475,4 +453,38 @@ private extension AppDelegate {
} }
}) })
} }
}
@MainActor func handleMarkAsStarred(userInfo: [AnyHashable: Any]) {
guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {
return
}
resumeDatabaseProcessingIfNecessary()
guard let account = AccountManager.shared.existingAccount(with: articlePathInfo.accountID) else {
os_log(.debug, "No account found from notification.")
return
}
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? { func copyHomePageAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let homePageURL = feed.homePageURL, let homePageURL = feed.homePageURL,
let url = URL(string: homePageURL) else { let url = URL(string: homePageURL) else {
@ -1001,9 +1002,12 @@ private extension SidebarViewController {
} }
func markAllAsReadAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { func markAllAsReadAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
feed.unreadCount > 0, feed.unreadCount > 0 else {
let articles = try? feed.fetchArticles(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil
}
guard let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
return nil return nil
} }
@ -1016,10 +1020,15 @@ private extension SidebarViewController {
let action = UIAlertAction(title: title, style: .default) { [weak self] action in 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 MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView, cancelCompletion: cancel) { [weak self] in
Task { @MainActor in
if let articles = try? await feed.fetchUnreadArticles() {
self?.coordinator.markAllAsRead(Array(articles)) self?.coordinator.markAllAsRead(Array(articles))
}
completion(true) completion(true)
} }
} }
}
return action return action
} }
@ -1092,11 +1101,14 @@ private extension SidebarViewController {
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in 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 MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
if let articles = try? feed.fetchUnreadArticles() {
Task { @MainActor in
if let articles = try? await feed.fetchUnreadArticles() {
self?.coordinator.markAllAsRead(Array(articles)) self?.coordinator.markAllAsRead(Array(articles))
} }
} }
} }
}
return action return action
} }
@ -1112,12 +1124,15 @@ private extension SidebarViewController {
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in 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 // If you don't have this delay the screen flashes when it executes this code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let articles = try? account.fetchArticles(.unread()) {
Task { @MainActor in
if let articles = try? await account.articles(for: .unread()) {
self?.coordinator.markAllAsRead(Array(articles)) self?.coordinator.markAllAsRead(Array(articles))
} }
} }
} }
} }
}
return action return action
} }

View File

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

View File

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