mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Continue fixing concurrency warnings.
This commit is contained in:
@@ -20,8 +20,8 @@ import UniformTypeIdentifiers
|
||||
import CoreSpotlight
|
||||
#endif
|
||||
|
||||
class ActivityManager {
|
||||
|
||||
@MainActor final class ActivityManager {
|
||||
|
||||
private var nextUnreadActivity: NSUserActivity?
|
||||
private var selectingActivity: NSUserActivity?
|
||||
private var readingActivity: NSUserActivity?
|
||||
@@ -264,8 +264,8 @@ private extension ActivityManager {
|
||||
return value?.components(separatedBy: " ").filter { $0.count > 2 } ?? []
|
||||
}
|
||||
|
||||
func updateSelectingActivityFeedSearchAttributes(with feed: Feed) {
|
||||
|
||||
@MainActor func updateSelectingActivityFeedSearchAttributes(with feed: Feed) {
|
||||
|
||||
let attributeSet = CSSearchableItemAttributeSet(contentType: UTType.item)
|
||||
attributeSet.title = feed.nameForDisplay
|
||||
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
|
||||
|
||||
@@ -19,11 +19,12 @@ public enum ArticleExtractorState {
|
||||
}
|
||||
|
||||
protocol ArticleExtractorDelegate {
|
||||
func articleExtractionDidFail(with: Error)
|
||||
func articleExtractionDidComplete(extractedArticle: ExtractedArticle)
|
||||
|
||||
@MainActor func articleExtractionDidFail(with: Error)
|
||||
@MainActor func articleExtractionDidComplete(extractedArticle: ExtractedArticle)
|
||||
}
|
||||
|
||||
class ArticleExtractor {
|
||||
final class ArticleExtractor {
|
||||
|
||||
private var dataTask: URLSessionDataTask? = nil
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import RSCore
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
struct ArticleRenderer {
|
||||
@MainActor struct ArticleRenderer {
|
||||
|
||||
typealias Rendering = (style: String, html: String, title: String, baseURL: String)
|
||||
|
||||
@@ -30,10 +30,10 @@ struct ArticleRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
static var imageIconScheme = "nnwImageIcon"
|
||||
|
||||
static var blank = Page(name: "blank")
|
||||
static var page = Page(name: "page")
|
||||
static let imageIconScheme = "nnwImageIcon"
|
||||
|
||||
static let blank = Page(name: "blank")
|
||||
static let page = Page(name: "page")
|
||||
|
||||
private let article: Article?
|
||||
private let extractedArticle: ExtractedArticle?
|
||||
@@ -328,7 +328,7 @@ private extension ArticleRenderer {
|
||||
|
||||
// MARK: - Article extension
|
||||
|
||||
private extension Article {
|
||||
@MainActor private extension Article {
|
||||
|
||||
var baseURL: URL? {
|
||||
var s = link
|
||||
|
||||
@@ -10,22 +10,20 @@ import Foundation
|
||||
|
||||
struct ArticlePathInfo {
|
||||
|
||||
let accountID: String
|
||||
let articleID: String
|
||||
let accountID: String?
|
||||
let accountName: String?
|
||||
let articleID: String?
|
||||
let feedID: 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
|
||||
self.accountID = articlePathUserInfo[ArticlePathKey.accountID]
|
||||
self.accountName = articlePathUserInfo[ArticlePathKey.accountName]
|
||||
self.articleID = articlePathUserInfo[ArticlePathKey.articleID]
|
||||
self.feedID = articlePathUserInfo[ArticlePathKey.feedID]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import Articles
|
||||
|
||||
// Mark articles read/unread, starred/unstarred, deleted/undeleted.
|
||||
|
||||
final class MarkStatusCommand: UndoableCommand {
|
||||
@MainActor final class MarkStatusCommand: UndoableCommand {
|
||||
|
||||
let undoActionName: String
|
||||
let redoActionName: String
|
||||
@@ -50,12 +50,12 @@ final class MarkStatusCommand: UndoableCommand {
|
||||
self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager, completion: completion)
|
||||
}
|
||||
|
||||
func perform() {
|
||||
@MainActor func perform() {
|
||||
mark(statusKey, flag)
|
||||
registerUndo()
|
||||
}
|
||||
|
||||
func undo() {
|
||||
@MainActor func undo() {
|
||||
mark(statusKey, !flag)
|
||||
registerRedo()
|
||||
}
|
||||
@@ -63,7 +63,7 @@ final class MarkStatusCommand: UndoableCommand {
|
||||
|
||||
private extension MarkStatusCommand {
|
||||
|
||||
func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
||||
@MainActor func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
||||
markArticles(articles, statusKey: statusKey, flag: flag, completion: completion)
|
||||
completion = nil
|
||||
}
|
||||
@@ -83,7 +83,7 @@ private extension MarkStatusCommand {
|
||||
}
|
||||
}
|
||||
|
||||
static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] {
|
||||
@MainActor static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] {
|
||||
|
||||
return articles.filter{ article in
|
||||
guard article.status.boolStatus(forKey: statusKey) != flag else { return false }
|
||||
|
||||
@@ -21,7 +21,7 @@ final class SendToMarsEditCommand: SendToCommand {
|
||||
appToUse() != nil
|
||||
}
|
||||
|
||||
func sendObject(_ object: Any?, selectedText: String?) {
|
||||
@MainActor func sendObject(_ object: Any?, selectedText: String?) {
|
||||
|
||||
guard canSendObject(object, selectedText: selectedText) else {
|
||||
return
|
||||
@@ -39,7 +39,7 @@ final class SendToMarsEditCommand: SendToCommand {
|
||||
|
||||
private extension SendToMarsEditCommand {
|
||||
|
||||
func send(_ article: Article, to app: UserApp) {
|
||||
@MainActor func send(_ article: Article, to app: UserApp) {
|
||||
|
||||
// App has already been launched.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ final class SendToMicroBlogCommand: SendToCommand {
|
||||
return true
|
||||
}
|
||||
|
||||
func sendObject(_ object: Any?, selectedText: String?) {
|
||||
@MainActor func sendObject(_ object: Any?, selectedText: String?) {
|
||||
|
||||
guard canSendObject(object, selectedText: selectedText) else {
|
||||
return
|
||||
@@ -60,7 +60,7 @@ final class SendToMicroBlogCommand: SendToCommand {
|
||||
|
||||
private extension Article {
|
||||
|
||||
var attributionString: String {
|
||||
@MainActor var attributionString: String {
|
||||
|
||||
// Feed name, or feed name + author name (if author is specified per-article).
|
||||
// Includes trailing space.
|
||||
|
||||
@@ -11,7 +11,7 @@ import Account
|
||||
|
||||
struct AddFeedDefaultContainer {
|
||||
|
||||
static var defaultContainer: Container? {
|
||||
@MainActor static var defaultContainer: Container? {
|
||||
|
||||
if let accountID = AppDefaults.shared.addFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) {
|
||||
if let folderName = AppDefaults.shared.addFeedFolderName, let folder = account.existingFolder(withDisplayName: folderName) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import Articles
|
||||
import RSParser
|
||||
|
||||
struct ArticleStringFormatter {
|
||||
@MainActor struct ArticleStringFormatter {
|
||||
|
||||
private static var feedNameCache = [String: String]()
|
||||
private static var titleCache = [String: String]()
|
||||
|
||||
@@ -13,7 +13,7 @@ import Account
|
||||
|
||||
// These handle multiple accounts.
|
||||
|
||||
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
|
||||
@MainActor func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
|
||||
|
||||
let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)
|
||||
|
||||
@@ -42,7 +42,7 @@ private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String:
|
||||
|
||||
extension Article {
|
||||
|
||||
var feed: Feed? {
|
||||
@MainActor var feed: Feed? {
|
||||
return account?.existingFeed(withFeedID: feedID)
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ extension Article {
|
||||
return datePublished ?? dateModified ?? status.dateArrived
|
||||
}
|
||||
|
||||
var isAvailableToMarkUnread: Bool {
|
||||
@MainActor var isAvailableToMarkUnread: Bool {
|
||||
guard let markUnreadWindow = account?.behaviors.compactMap( { behavior -> Int? in
|
||||
switch behavior {
|
||||
case .disallowMarkAsUnreadAfterPeriod(let days):
|
||||
@@ -117,11 +117,11 @@ extension Article {
|
||||
}
|
||||
}
|
||||
|
||||
func iconImage() -> IconImage? {
|
||||
@MainActor func iconImage() -> IconImage? {
|
||||
return IconImageCache.shared.imageForArticle(self)
|
||||
}
|
||||
|
||||
func iconImageUrl(feed: Feed) -> URL? {
|
||||
@MainActor func iconImageUrl(feed: Feed) -> URL? {
|
||||
if let image = iconImage() {
|
||||
let fm = FileManager.default
|
||||
var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
@@ -138,7 +138,7 @@ extension Article {
|
||||
}
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
@MainActor func byline() -> String {
|
||||
guard let authors = authors ?? feed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
@@ -199,7 +199,7 @@ struct ArticlePathKey {
|
||||
|
||||
extension Article {
|
||||
|
||||
public var pathUserInfo: [AnyHashable : Any] {
|
||||
@MainActor public var pathUserInfo: [AnyHashable : Any] {
|
||||
return [
|
||||
ArticlePathKey.accountID: accountID,
|
||||
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
|
||||
@@ -214,7 +214,7 @@ extension Article {
|
||||
|
||||
extension Article: SortableArticle {
|
||||
|
||||
var sortableName: String {
|
||||
@MainActor var sortableName: String {
|
||||
return feed?.name ?? ""
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ extension Account: SmallIconProvider {
|
||||
|
||||
extension Feed: SmallIconProvider {
|
||||
|
||||
var smallIcon: IconImage? {
|
||||
@MainActor var smallIcon: IconImage? {
|
||||
if let iconImage = appDelegate.faviconDownloader.favicon(for: self) {
|
||||
return iconImage
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import RSCore
|
||||
import Account
|
||||
|
||||
final class FaviconGenerator {
|
||||
@MainActor final class FaviconGenerator {
|
||||
|
||||
private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import Account
|
||||
import Articles
|
||||
|
||||
final class IconImageCache {
|
||||
@MainActor final class IconImageCache {
|
||||
|
||||
static let shared = IconImageCache()
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
struct DefaultFeedsImporter {
|
||||
@MainActor struct DefaultFeedsImporter {
|
||||
|
||||
static func importDefaultFeeds(account: Account) {
|
||||
let defaultFeedsURL = Bundle.main.url(forResource: "DefaultFeeds", withExtension: "opml")!
|
||||
|
||||
@@ -14,9 +14,9 @@ import Account
|
||||
|
||||
final class ExtensionContainersFile {
|
||||
|
||||
private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionContainersFile")
|
||||
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionContainersFile")
|
||||
|
||||
private static var filePath: String = {
|
||||
private static let filePath: String = {
|
||||
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
|
||||
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
|
||||
return containerURL!.appendingPathComponent("extension_containers.plist").path
|
||||
|
||||
@@ -10,8 +10,8 @@ import Foundation
|
||||
import os.log
|
||||
import Account
|
||||
|
||||
final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
|
||||
|
||||
final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Sendable {
|
||||
|
||||
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "extensionFeedAddRequestFile")
|
||||
|
||||
private static let filePath: String = {
|
||||
@@ -30,7 +30,7 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
|
||||
return operationQueue
|
||||
}
|
||||
|
||||
override init() {
|
||||
@MainActor override init() {
|
||||
operationQueue = OperationQueue()
|
||||
operationQueue.maxConcurrentOperationCount = 1
|
||||
|
||||
@@ -41,14 +41,16 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
|
||||
}
|
||||
|
||||
func presentedItemDidChange() {
|
||||
DispatchQueue.main.async {
|
||||
Task { @MainActor in
|
||||
self.process()
|
||||
}
|
||||
}
|
||||
|
||||
func resume() {
|
||||
NSFileCoordinator.addFilePresenter(self)
|
||||
process()
|
||||
Task { @MainActor in
|
||||
process()
|
||||
}
|
||||
}
|
||||
|
||||
func suspend() {
|
||||
@@ -95,8 +97,8 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter {
|
||||
|
||||
private extension ExtensionFeedAddRequestFile {
|
||||
|
||||
func process() {
|
||||
|
||||
@MainActor func process() {
|
||||
|
||||
let decoder = PropertyListDecoder()
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
@@ -130,7 +132,7 @@ private extension ExtensionFeedAddRequestFile {
|
||||
requests?.forEach { processRequest($0) }
|
||||
}
|
||||
|
||||
func processRequest(_ request: ExtensionFeedAddRequest) {
|
||||
@MainActor func processRequest(_ request: ExtensionFeedAddRequest) {
|
||||
var destinationAccountID: String? = nil
|
||||
switch request.destinationContainerID {
|
||||
case .account(let accountID):
|
||||
|
||||
@@ -62,7 +62,7 @@ final class SmartFeed: PseudoFeed {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func fetchUnreadCounts() {
|
||||
@objc @MainActor func fetchUnreadCounts() {
|
||||
let activeAccounts = AccountManager.shared.activeAccounts
|
||||
|
||||
// Remove any accounts that are no longer active or have been deleted
|
||||
@@ -113,15 +113,18 @@ private extension SmartFeed {
|
||||
|
||||
func fetchUnreadCount(for account: Account) {
|
||||
delegate.fetchUnreadCount(for: account) { singleUnreadCountResult in
|
||||
guard let accountUnreadCount = try? singleUnreadCountResult.get() else {
|
||||
return
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
guard let accountUnreadCount = try? singleUnreadCountResult.get() else {
|
||||
return
|
||||
}
|
||||
self.unreadCounts[account.accountID] = accountUnreadCount
|
||||
self.updateUnreadCount()
|
||||
}
|
||||
self.unreadCounts[account.accountID] = accountUnreadCount
|
||||
self.updateUnreadCount()
|
||||
}
|
||||
}
|
||||
|
||||
func updateUnreadCount() {
|
||||
@MainActor func updateUnreadCount() {
|
||||
unreadCount = AccountManager.shared.activeAccounts.reduce(0) { (result, account) -> Int in
|
||||
if let oneUnreadCount = unreadCounts[account.accountID] {
|
||||
return result + oneUnreadCount
|
||||
|
||||
@@ -51,13 +51,13 @@ final class UnreadFeed: PseudoFeed {
|
||||
}
|
||||
#endif
|
||||
|
||||
init() {
|
||||
@MainActor init() {
|
||||
|
||||
self.unreadCount = appDelegate.unreadCount
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: appDelegate)
|
||||
}
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
@objc @MainActor func unreadCountDidChange(_ note: Notification) {
|
||||
|
||||
assert(note.object is AppDelegate)
|
||||
unreadCount = appDelegate.unreadCount
|
||||
|
||||
@@ -67,7 +67,7 @@ extension Array where Element == Article {
|
||||
return false
|
||||
}
|
||||
|
||||
func anyArticleIsReadAndCanMarkUnread() -> Bool {
|
||||
@MainActor func anyArticleIsReadAndCanMarkUnread() -> Bool {
|
||||
return anyArticlePassesTest { $0.status.read && $0.isAvailableToMarkUnread }
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ extension Array where Element == Article {
|
||||
var i = 0
|
||||
for article in self {
|
||||
let otherArticle = otherArticles[i]
|
||||
if article.account != otherArticle.account || article.articleID != otherArticle.articleID {
|
||||
if article.accountID != otherArticle.accountID || article.articleID != otherArticle.articleID {
|
||||
return false
|
||||
}
|
||||
i += 1
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
class AccountRefreshTimer {
|
||||
@MainActor final class AccountRefreshTimer {
|
||||
|
||||
var shuttingDown = false
|
||||
|
||||
@@ -64,7 +64,7 @@ class AccountRefreshTimer {
|
||||
|
||||
}
|
||||
|
||||
@objc func timedRefresh(_ sender: Timer?) {
|
||||
@objc @MainActor func timedRefresh(_ sender: Timer?) {
|
||||
|
||||
guard !shuttingDown else {
|
||||
return
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
class ArticleStatusSyncTimer {
|
||||
|
||||
@MainActor final class ArticleStatusSyncTimer {
|
||||
|
||||
private static let intervalSeconds = Double(120)
|
||||
|
||||
var shuttingDown = false
|
||||
|
||||
@@ -24,7 +24,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
filterExceptions = Set<SidebarItemIdentifier>()
|
||||
}
|
||||
|
||||
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||
@MainActor func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||
if node.isRoot {
|
||||
return childNodesForRootNode(node)
|
||||
}
|
||||
@@ -41,7 +41,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
private extension FeedTreeControllerDelegate {
|
||||
|
||||
func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
|
||||
@MainActor func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
|
||||
var topLevelNodes = [Node]()
|
||||
|
||||
let smartFeedsNode = rootNode.existingOrNewChildNode(with: SmartFeedsController.shared)
|
||||
@@ -132,7 +132,7 @@ private extension FeedTreeControllerDelegate {
|
||||
return node
|
||||
}
|
||||
|
||||
func sortedAccountNodes(_ parent: Node) -> [Node] {
|
||||
@MainActor func sortedAccountNodes(_ parent: Node) -> [Node] {
|
||||
let nodes = AccountManager.shared.sortedActiveAccounts.compactMap { (account) -> Node? in
|
||||
let accountNode = parent.existingOrNewChildNode(with: account)
|
||||
accountNode.canHaveChildNodes = true
|
||||
|
||||
@@ -14,7 +14,7 @@ import Account
|
||||
|
||||
final class FolderTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||
@MainActor func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||
|
||||
return node.isRoot ? childNodesForRootNode(node) : childNodes(node)
|
||||
}
|
||||
@@ -22,8 +22,8 @@ final class FolderTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
private extension FolderTreeControllerDelegate {
|
||||
|
||||
func childNodesForRootNode(_ node: Node) -> [Node]? {
|
||||
|
||||
@MainActor func childNodesForRootNode(_ node: Node) -> [Node]? {
|
||||
|
||||
let accountNodes: [Node] = AccountManager.shared.sortedActiveAccounts.map { account in
|
||||
let accountNode = Node(representedObject: account, parent: node)
|
||||
accountNode.canHaveChildNodes = true
|
||||
|
||||
@@ -25,9 +25,11 @@ final class UserNotificationManager: NSObject {
|
||||
return
|
||||
}
|
||||
|
||||
for article in articles {
|
||||
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
|
||||
sendNotification(feed: feed, article: article)
|
||||
Task { @MainActor in
|
||||
for article in articles {
|
||||
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
|
||||
sendNotification(feed: feed, article: article)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +55,7 @@ final class UserNotificationManager: NSObject {
|
||||
|
||||
private extension UserNotificationManager {
|
||||
|
||||
func sendNotification(feed: Feed, article: Article) {
|
||||
@MainActor func sendNotification(feed: Feed, article: Article) {
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
content.title = feed.nameForDisplay
|
||||
@@ -79,7 +81,7 @@ private extension UserNotificationManager {
|
||||
/// - feed: `Feed`
|
||||
/// - Returns: A `UNNotifcationAttachment` if an icon is available. Otherwise nil.
|
||||
/// - Warning: In certain scenarios, this will return the `faviconTemplateImage`.
|
||||
func thumbnailAttachment(for article: Article, feed: Feed) -> UNNotificationAttachment? {
|
||||
@MainActor func thumbnailAttachment(for article: Article, feed: Feed) -> UNNotificationAttachment? {
|
||||
if let imageURL = article.iconImageUrl(feed: feed) {
|
||||
let thumbnail = try? UNNotificationAttachment(identifier: feed.feedID, url: imageURL, options: nil)
|
||||
return thumbnail
|
||||
|
||||
Reference in New Issue
Block a user