Merge branch 'main' of https://github.com/Ranchero-Software/NetNewsWire into main
This commit is contained in:
commit
634563836f
@ -455,7 +455,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
mainWindowController?.handle(response)
|
||||
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
|
||||
switch response.actionIdentifier {
|
||||
case "MARK_AS_READ":
|
||||
handleMarkAsRead(userInfo: userInfo)
|
||||
case "MARK_AS_STARRED":
|
||||
handleMarkAsStarred(userInfo: userInfo)
|
||||
default:
|
||||
mainWindowController?.handle(response)
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
@ -791,3 +801,47 @@ extension AppDelegate: NSWindowRestoration {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle Notification Actions
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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: .starred, flag: true)
|
||||
}
|
||||
}
|
||||
|
@ -52,11 +52,11 @@ final class IconView: UIView {
|
||||
}
|
||||
|
||||
private var isSymbolImage: Bool {
|
||||
return imageView.image?.isSymbolImage ?? false
|
||||
return iconImage?.isSymbol ?? false
|
||||
}
|
||||
|
||||
private var isBackgroundSuppressed: Bool {
|
||||
return imageView.image?.isBackgroundSuppressed ?? false
|
||||
return iconImage?.isBackgroundSupressed ?? false
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
@ -132,7 +132,13 @@
|
||||
17D5F17124B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; };
|
||||
17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; };
|
||||
17D5F19524B0C1DD00375168 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; };
|
||||
17E0084625941887000C23F0 /* SizeCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E0084525941887000C23F0 /* SizeCategories.swift */; };
|
||||
17E4DBD624BFC53E00FE462A /* AdvancedPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */; };
|
||||
27B86EEB25A53AAB00264340 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4A24D343A500E90810 /* Account */; };
|
||||
27B86EEC25A53AAB00264340 /* Articles in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0080E25936DF6000C23F0 /* Articles */; };
|
||||
27B86EED25A53AAB00264340 /* ArticlesDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0081125936DF6000C23F0 /* ArticlesDatabase */; };
|
||||
27B86EEE25A53AAB00264340 /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0081425936DFF000C23F0 /* Secrets */; };
|
||||
27B86EEF25A53AAB00264340 /* SyncDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0081725936DFF000C23F0 /* SyncDatabase */; };
|
||||
3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; };
|
||||
3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; };
|
||||
3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; };
|
||||
@ -253,6 +259,19 @@
|
||||
513C5CEC232571C2003D4054 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 513C5CEA232571C2003D4054 /* MainInterface.storyboard */; };
|
||||
513C5CF0232571C2003D4054 /* NetNewsWire iOS Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
513CCF2524880C1500C55709 /* MasterFeedTableViewIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513CCF08248808BA00C55709 /* MasterFeedTableViewIdentifier.swift */; };
|
||||
513F325C2593ECF40003048F /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 513F325B2593ECF40003048F /* RSCore */; };
|
||||
513F325D2593ECF40003048F /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F325B2593ECF40003048F /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
513F32712593EE6F0003048F /* Articles in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32702593EE6F0003048F /* Articles */; };
|
||||
513F32722593EE6F0003048F /* Articles in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32702593EE6F0003048F /* Articles */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
513F32742593EE6F0003048F /* ArticlesDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32732593EE6F0003048F /* ArticlesDatabase */; };
|
||||
513F32752593EE6F0003048F /* ArticlesDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32732593EE6F0003048F /* ArticlesDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
513F32772593EE6F0003048F /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32762593EE6F0003048F /* Secrets */; };
|
||||
513F32782593EE6F0003048F /* Secrets in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32762593EE6F0003048F /* Secrets */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
513F327A2593EE6F0003048F /* SyncDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32792593EE6F0003048F /* SyncDatabase */; };
|
||||
513F327B2593EE6F0003048F /* SyncDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32792593EE6F0003048F /* SyncDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
513F32812593EF180003048F /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 516B695E24D2F33B00B5702F /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
513F32882593EF8F0003048F /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; };
|
||||
513F32892593EF8F0003048F /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51408B7E24A9EC6F0073CF4E /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */; };
|
||||
51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */; };
|
||||
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */; };
|
||||
@ -501,7 +520,6 @@
|
||||
51BC2F3824D3439A00E90810 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F3724D3439A00E90810 /* Account */; };
|
||||
51BC2F4824D3439E00E90810 /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4724D3439E00E90810 /* RSTree */; };
|
||||
51BC2F4924D3439E00E90810 /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4724D3439E00E90810 /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51BC2F4B24D343A500E90810 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4A24D343A500E90810 /* Account */; };
|
||||
51BC2F4D24D343AB00E90810 /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4C24D343AB00E90810 /* RSTree */; };
|
||||
51BC2F4E24D343AB00E90810 /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4C24D343AB00E90810 /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; };
|
||||
@ -586,6 +604,15 @@
|
||||
51DC37072402153E0095D371 /* UpdateSelectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37062402153E0095D371 /* UpdateSelectionOperation.swift */; };
|
||||
51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37082402F1470095D371 /* MasterFeedDataSourceOperation.swift */; };
|
||||
51DC370B2405BC9A0095D371 /* PreloadedWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC370A2405BC9A0095D371 /* PreloadedWebView.swift */; };
|
||||
51E0614525A5A28E00194066 /* Articles in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614425A5A28E00194066 /* Articles */; };
|
||||
51E0614625A5A28E00194066 /* Articles in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614425A5A28E00194066 /* Articles */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51E0614825A5A28E00194066 /* ArticlesDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614725A5A28E00194066 /* ArticlesDatabase */; };
|
||||
51E0614925A5A28E00194066 /* ArticlesDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614725A5A28E00194066 /* ArticlesDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51E0614B25A5A28E00194066 /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614A25A5A28E00194066 /* Secrets */; };
|
||||
51E0614C25A5A28E00194066 /* Secrets in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614A25A5A28E00194066 /* Secrets */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51E0614E25A5A28E00194066 /* SyncDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614D25A5A28E00194066 /* SyncDatabase */; };
|
||||
51E0614F25A5A28E00194066 /* SyncDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614D25A5A28E00194066 /* SyncDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51E0615125A5A29600194066 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0615025A5A29600194066 /* CrashReporter */; };
|
||||
51E36E71239D6610006F47A5 /* AddFeedSelectFolderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E36E70239D6610006F47A5 /* AddFeedSelectFolderTableViewCell.swift */; };
|
||||
51E36E8C239D6765006F47A5 /* AddFeedSelectFolderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51E36E8B239D6765006F47A5 /* AddFeedSelectFolderTableViewCell.xib */; };
|
||||
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; };
|
||||
@ -1211,6 +1238,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
513F325D2593ECF40003048F /* RSCore in Embed Frameworks */,
|
||||
51BC2F4924D3439E00E90810 /* RSTree in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
@ -1222,6 +1250,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
513F32892593EF8F0003048F /* RSCore in Embed Frameworks */,
|
||||
51BC2F4E24D343AB00E90810 /* RSTree in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
@ -1257,11 +1286,16 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
513F32782593EE6F0003048F /* Secrets in Embed Frameworks */,
|
||||
5138E95324D3418100AFF0FE /* RSParser in Embed Frameworks */,
|
||||
5138E94A24D3416D00AFF0FE /* RSCore in Embed Frameworks */,
|
||||
5138E95924D3419000AFF0FE /* RSWeb in Embed Frameworks */,
|
||||
513F327B2593EE6F0003048F /* SyncDatabase in Embed Frameworks */,
|
||||
513F32722593EE6F0003048F /* Articles in Embed Frameworks */,
|
||||
513F32812593EF180003048F /* Account in Embed Frameworks */,
|
||||
5138E93B24D33E5600AFF0FE /* RSTree in Embed Frameworks */,
|
||||
5138E94D24D3417A00AFF0FE /* RSDatabase in Embed Frameworks */,
|
||||
513F32752593EE6F0003048F /* ArticlesDatabase in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -1287,11 +1321,15 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
51E0614C25A5A28E00194066 /* Secrets in Embed Frameworks */,
|
||||
17386BA52577C6240014C8B2 /* RSParser in Embed Frameworks */,
|
||||
17386B9F2577C6240014C8B2 /* RSDatabase in Embed Frameworks */,
|
||||
17386B9C2577C6240014C8B2 /* RSWeb in Embed Frameworks */,
|
||||
51E0614F25A5A28E00194066 /* SyncDatabase in Embed Frameworks */,
|
||||
51E0614625A5A28E00194066 /* Articles in Embed Frameworks */,
|
||||
17386B962577C6240014C8B2 /* RSCore in Embed Frameworks */,
|
||||
17386B992577C6240014C8B2 /* RSTree in Embed Frameworks */,
|
||||
51E0614925A5A28E00194066 /* ArticlesDatabase in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -1454,6 +1492,7 @@
|
||||
17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedModel.swift; sourceTree = "<group>"; };
|
||||
17D3CEE2257C4D2300E74939 /* AddAccountSignUp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountSignUp.swift; sourceTree = "<group>"; };
|
||||
17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModel.swift; sourceTree = "<group>"; };
|
||||
17E0084525941887000C23F0 /* SizeCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeCategories.swift; sourceTree = "<group>"; };
|
||||
17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesModel.swift; sourceTree = "<group>"; };
|
||||
3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = "<group>"; };
|
||||
3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = "<group>"; };
|
||||
@ -2024,7 +2063,8 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
51BC2F4B24D343A500E90810 /* Account in Frameworks */,
|
||||
27B86EEB25A53AAB00264340 /* Account in Frameworks */,
|
||||
513F32882593EF8F0003048F /* RSCore in Frameworks */,
|
||||
51BC2F4D24D343AB00E90810 /* RSTree in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -2034,6 +2074,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
51BC2F3824D3439A00E90810 /* Account in Frameworks */,
|
||||
513F325C2593ECF40003048F /* RSCore in Frameworks */,
|
||||
51BC2F4824D3439E00E90810 /* RSTree in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -2055,7 +2096,11 @@
|
||||
17A1597C24E3DEDD005DA32A /* RSCore in Frameworks */,
|
||||
516B695D24D2F28E00B5702F /* Account in Frameworks */,
|
||||
17A1598524E3DEDD005DA32A /* RSDatabase in Frameworks */,
|
||||
27B86EEC25A53AAB00264340 /* Articles in Frameworks */,
|
||||
27B86EEE25A53AAB00264340 /* Secrets in Frameworks */,
|
||||
51E4989724A8065700B667CB /* CloudKit.framework in Frameworks */,
|
||||
27B86EED25A53AAB00264340 /* ArticlesDatabase in Frameworks */,
|
||||
27B86EEF25A53AAB00264340 /* SyncDatabase in Frameworks */,
|
||||
51E4989924A8067000B667CB /* WebKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -2064,13 +2109,18 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
51E0614E25A5A28E00194066 /* SyncDatabase in Frameworks */,
|
||||
51E0614825A5A28E00194066 /* ArticlesDatabase in Frameworks */,
|
||||
17386BA42577C6240014C8B2 /* RSParser in Frameworks */,
|
||||
17386B952577C6240014C8B2 /* RSCore in Frameworks */,
|
||||
51E0614B25A5A28E00194066 /* Secrets in Frameworks */,
|
||||
17386B6C2577BD820014C8B2 /* RSSparkle in Frameworks */,
|
||||
51E0615125A5A29600194066 /* CrashReporter in Frameworks */,
|
||||
516B695B24D2F28600B5702F /* Account in Frameworks */,
|
||||
17386B9B2577C6240014C8B2 /* RSWeb in Frameworks */,
|
||||
17386B9E2577C6240014C8B2 /* RSDatabase in Frameworks */,
|
||||
17386BB62577C7340014C8B2 /* RSCoreResources in Frameworks */,
|
||||
51E0614525A5A28E00194066 /* Articles in Frameworks */,
|
||||
51E498B124A806A400B667CB /* CloudKit.framework in Frameworks */,
|
||||
51E498B324A806AA00B667CB /* WebKit.framework in Frameworks */,
|
||||
17386B982577C6240014C8B2 /* RSTree in Frameworks */,
|
||||
@ -2110,7 +2160,11 @@
|
||||
5138E95224D3418100AFF0FE /* RSParser in Frameworks */,
|
||||
5138E94C24D3417A00AFF0FE /* RSDatabase in Frameworks */,
|
||||
51C452B42265141B00C03939 /* WebKit.framework in Frameworks */,
|
||||
513F32712593EE6F0003048F /* Articles in Frameworks */,
|
||||
513F32772593EE6F0003048F /* Secrets in Frameworks */,
|
||||
51E4DB082425F9EB0091EB5B /* CloudKit.framework in Frameworks */,
|
||||
513F32742593EE6F0003048F /* ArticlesDatabase in Frameworks */,
|
||||
513F327A2593EE6F0003048F /* SyncDatabase in Frameworks */,
|
||||
5138E93A24D33E5600AFF0FE /* RSTree in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -2146,12 +2200,12 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
171BCBB124CBD569006E22D9 /* Account Management */ = {
|
||||
171BCBB124CBD569006E22D9 /* Account Management */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */,
|
||||
);
|
||||
path = "Account Management ";
|
||||
path = "Account Management";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
172199EB24AB228E00A31D04 /* Settings */ = {
|
||||
@ -2264,6 +2318,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
176814562564BD0600D98635 /* ArticleItemView.swift */,
|
||||
17E0084525941887000C23F0 /* SizeCategories.swift */,
|
||||
);
|
||||
path = "Shared Views";
|
||||
sourceTree = "<group>";
|
||||
@ -2756,6 +2811,7 @@
|
||||
51E49A0224A91FF600B667CB /* SceneNavigationView.swift */,
|
||||
1704053324E5985A00A00787 /* SceneNavigationModel.swift */,
|
||||
51C0513824A77DF800194D5E /* Assets.xcassets */,
|
||||
171BCBB124CBD569006E22D9 /* Account Management */,
|
||||
17930ED224AF10CD00A9BA52 /* Add */,
|
||||
51A576B924AE617B00078888 /* Article */,
|
||||
51A8001024CA0FAE00F41F1D /* CombineExt */,
|
||||
@ -2765,7 +2821,6 @@
|
||||
51E499FB24A9135A00B667CB /* Sidebar */,
|
||||
514E6C0424AD2B0400AC6F6E /* SwiftUI Extensions */,
|
||||
51919FCB24AB855000541E64 /* Timeline */,
|
||||
171BCBB124CBD569006E22D9 /* Account Management */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
@ -3691,6 +3746,7 @@
|
||||
51314634235A7BBE00387FDC /* Frameworks */,
|
||||
51314635235A7BBE00387FDC /* Resources */,
|
||||
5102AE7724D17FB50050839C /* Embed Frameworks */,
|
||||
513F328A2593EFCE0003048F /* Delete Unnecessary Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -3700,6 +3756,7 @@
|
||||
packageProductDependencies = (
|
||||
51BC2F4A24D343A500E90810 /* Account */,
|
||||
51BC2F4C24D343AB00E90810 /* RSTree */,
|
||||
513F32872593EF8F0003048F /* RSCore */,
|
||||
);
|
||||
productName = "NetNewsWire iOS Intents Extension";
|
||||
productReference = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */;
|
||||
@ -3713,6 +3770,7 @@
|
||||
513C5CE3232571C2003D4054 /* Frameworks */,
|
||||
513C5CE4232571C2003D4054 /* Resources */,
|
||||
5102AE7324D17FAA0050839C /* Embed Frameworks */,
|
||||
513F328B2593F03F0003048F /* Delete Unnecessary Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -3722,6 +3780,7 @@
|
||||
packageProductDependencies = (
|
||||
51BC2F3724D3439A00E90810 /* Account */,
|
||||
51BC2F4724D3439E00E90810 /* RSTree */,
|
||||
513F325B2593ECF40003048F /* RSCore */,
|
||||
);
|
||||
productName = "NetNewsWire iOS Share Extension";
|
||||
productReference = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */;
|
||||
@ -3766,6 +3825,10 @@
|
||||
17A1598124E3DEDD005DA32A /* RSWeb */,
|
||||
17A1598424E3DEDD005DA32A /* RSDatabase */,
|
||||
17A1598724E3DEDD005DA32A /* RSParser */,
|
||||
17E0080E25936DF6000C23F0 /* Articles */,
|
||||
17E0081125936DF6000C23F0 /* ArticlesDatabase */,
|
||||
17E0081425936DFF000C23F0 /* Secrets */,
|
||||
17E0081725936DFF000C23F0 /* SyncDatabase */,
|
||||
);
|
||||
productName = iOS;
|
||||
productReference = 51C0513D24A77DF800194D5E /* NetNewsWire.app */;
|
||||
@ -3795,6 +3858,11 @@
|
||||
17386B9D2577C6240014C8B2 /* RSDatabase */,
|
||||
17386BA32577C6240014C8B2 /* RSParser */,
|
||||
17386BB52577C7340014C8B2 /* RSCoreResources */,
|
||||
51E0614425A5A28E00194066 /* Articles */,
|
||||
51E0614725A5A28E00194066 /* ArticlesDatabase */,
|
||||
51E0614A25A5A28E00194066 /* Secrets */,
|
||||
51E0614D25A5A28E00194066 /* SyncDatabase */,
|
||||
51E0615025A5A29600194066 /* CrashReporter */,
|
||||
);
|
||||
productName = macOS;
|
||||
productReference = 51C0514424A77DF800194D5E /* NetNewsWire.app */;
|
||||
@ -3886,6 +3954,10 @@
|
||||
5138E94B24D3417A00AFF0FE /* RSDatabase */,
|
||||
5138E95124D3418100AFF0FE /* RSParser */,
|
||||
5138E95724D3419000AFF0FE /* RSWeb */,
|
||||
513F32702593EE6F0003048F /* Articles */,
|
||||
513F32732593EE6F0003048F /* ArticlesDatabase */,
|
||||
513F32762593EE6F0003048F /* Secrets */,
|
||||
513F32792593EE6F0003048F /* SyncDatabase */,
|
||||
);
|
||||
productName = "NetNewsWire-iOS";
|
||||
productReference = 840D617C2029031C009BC708 /* NetNewsWire.app */;
|
||||
@ -4360,6 +4432,42 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Delete the framework that Xcode should have never included\n# https://forums.swift.org/t/is-this-an-xcode-bug-or-somehow-related-to-spm/33987\nrm -rf \"${TARGET_BUILD_DIR}/NetNewsWire Share Extension.appex/Contents/Frameworks\"\n";
|
||||
};
|
||||
513F328A2593EFCE0003048F /* Delete Unnecessary Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Delete Unnecessary Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Delete the framework that Xcode should have never included\n# https://forums.swift.org/t/is-this-an-xcode-bug-or-somehow-related-to-spm/33987\nrm -rf \"${TARGET_BUILD_DIR}/NetNewsWire iOS Intents Extension.appex/Frameworks\"\n";
|
||||
};
|
||||
513F328B2593F03F0003048F /* Delete Unnecessary Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Delete Unnecessary Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Delete the framework that Xcode should have never included\n# https://forums.swift.org/t/is-this-an-xcode-bug-or-somehow-related-to-spm/33987\nrm -rf \"${TARGET_BUILD_DIR}/NetNewsWire iOS Share Extension.appex/Frameworks\"\n";
|
||||
};
|
||||
515D50802326D02600EE1167 /* Run Script: Verify No Build Settings */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 8;
|
||||
@ -4475,6 +4583,7 @@
|
||||
176814652564BD7F00D98635 /* WidgetData.swift in Sources */,
|
||||
1768145E2564BD7B00D98635 /* WidgetDataDecoder.swift in Sources */,
|
||||
176814132564BC8A00D98635 /* WidgetBundle.swift in Sources */,
|
||||
17E0084625941887000C23F0 /* SizeCategories.swift in Sources */,
|
||||
176814462564BCD200D98635 /* StarredWidget.swift in Sources */,
|
||||
176814572564BD0600D98635 /* ArticleItemView.swift in Sources */,
|
||||
1768144E2564BCE000D98635 /* SmartFeedSummaryWidget.swift in Sources */,
|
||||
@ -6088,6 +6197,22 @@
|
||||
package = 51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */;
|
||||
productName = RSParser;
|
||||
};
|
||||
17E0080E25936DF6000C23F0 /* Articles */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Articles;
|
||||
};
|
||||
17E0081125936DF6000C23F0 /* ArticlesDatabase */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ArticlesDatabase;
|
||||
};
|
||||
17E0081425936DFF000C23F0 /* Secrets */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Secrets;
|
||||
};
|
||||
17E0081725936DFF000C23F0 /* SyncDatabase */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SyncDatabase;
|
||||
};
|
||||
5102AE6824D17F7C0050839C /* RSCore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */;
|
||||
@ -6144,6 +6269,32 @@
|
||||
package = 51383A3024D1F90E0027E272 /* XCRemoteSwiftPackageReference "RSWeb" */;
|
||||
productName = RSWeb;
|
||||
};
|
||||
513F325B2593ECF40003048F /* RSCore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */;
|
||||
productName = RSCore;
|
||||
};
|
||||
513F32702593EE6F0003048F /* Articles */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Articles;
|
||||
};
|
||||
513F32732593EE6F0003048F /* ArticlesDatabase */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ArticlesDatabase;
|
||||
};
|
||||
513F32762593EE6F0003048F /* Secrets */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Secrets;
|
||||
};
|
||||
513F32792593EE6F0003048F /* SyncDatabase */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SyncDatabase;
|
||||
};
|
||||
513F32872593EF8F0003048F /* RSCore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */;
|
||||
productName = RSCore;
|
||||
};
|
||||
514C16CD24D2E63F009A3AFA /* Account */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Account;
|
||||
@ -6217,6 +6368,27 @@
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Secrets;
|
||||
};
|
||||
51E0614425A5A28E00194066 /* Articles */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Articles;
|
||||
};
|
||||
51E0614725A5A28E00194066 /* ArticlesDatabase */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ArticlesDatabase;
|
||||
};
|
||||
51E0614A25A5A28E00194066 /* Secrets */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Secrets;
|
||||
};
|
||||
51E0614D25A5A28E00194066 /* SyncDatabase */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SyncDatabase;
|
||||
};
|
||||
51E0615025A5A29600194066 /* CrashReporter */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */;
|
||||
productName = CrashReporter;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 849C64581ED37A5D003D8FC0 /* Project object */;
|
||||
|
@ -1,7 +1,5 @@
|
||||
# ![Icon](Technotes/Images/icon.png) NetNewsWire
|
||||
|
||||
[![CI](https://github.com/Ranchero-Software/NetNewsWire/workflows/CI/badge.svg?branch=main)](https://github.com/Ranchero-Software/NetNewsWire/actions?query=workflow%3ACI+branch%3Amain)
|
||||
|
||||
It’s a free and open source feed reader for macOS and iOS.
|
||||
|
||||
It supports [RSS](http://cyber.harvard.edu/rss/rss.html), [Atom](https://tools.ietf.org/html/rfc4287), [JSON Feed](https://jsonfeed.org/), and [RSS-in-JSON](https://github.com/scripting/Scripting-News/blob/master/rss-in-json/README.md) formats.
|
||||
|
@ -103,6 +103,22 @@ extension Article {
|
||||
return FaviconGenerator.favicon(webFeed)
|
||||
}
|
||||
|
||||
func iconImageUrl(webFeed: WebFeed) -> URL? {
|
||||
if let image = iconImage() {
|
||||
let fm = FileManager.default
|
||||
var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
#if os(macOS)
|
||||
path.appendPathComponent(webFeed.webFeedID + "_smallIcon.tiff")
|
||||
#else
|
||||
path.appendPathComponent(webFeed.webFeedID + "_smallIcon.png")
|
||||
#endif
|
||||
fm.createFile(atPath: path.path, contents: image.image.dataRepresentation()!, attributes: nil)
|
||||
return path
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
guard let authors = authors ?? webFeed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
|
@ -17,6 +17,7 @@ final class UserNotificationManager: NSObject {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
|
||||
registerCategoriesAndActions()
|
||||
}
|
||||
|
||||
@objc func accountDidDownloadArticles(_ note: Notification) {
|
||||
@ -43,26 +44,55 @@ final class UserNotificationManager: NSObject {
|
||||
|
||||
private extension UserNotificationManager {
|
||||
|
||||
private func sendNotification(webFeed: WebFeed, article: Article) {
|
||||
func sendNotification(webFeed: WebFeed, article: Article) {
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
content.title = webFeed.nameForDisplay
|
||||
|
||||
if !ArticleStringFormatter.truncatedTitle(article).isEmpty {
|
||||
content.subtitle = ArticleStringFormatter.truncatedTitle(article)
|
||||
}
|
||||
|
||||
content.body = ArticleStringFormatter.truncatedSummary(article)
|
||||
|
||||
content.threadIdentifier = webFeed.webFeedID
|
||||
content.summaryArgument = "\(webFeed.nameForDisplay)"
|
||||
content.summaryArgumentCount = 1
|
||||
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = [UserInfoKey.articlePath: article.pathUserInfo]
|
||||
content.categoryIdentifier = "NEW_ARTICLE_NOTIFICATION_CATEGORY"
|
||||
if let attachment = thumbnailAttachment(for: article, webFeed: webFeed) {
|
||||
content.attachments.append(attachment)
|
||||
}
|
||||
|
||||
let request = UNNotificationRequest.init(identifier: "articleID:\(article.articleID)", content: content, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
}
|
||||
|
||||
/// Determine if there is an available icon for the article. This will then move it to the caches directory and make it avialble for the notification.
|
||||
/// - Parameters:
|
||||
/// - article: `Article`
|
||||
/// - webFeed: `WebFeed`
|
||||
/// - Returns: A `UNNotifcationAttachment` if an icon is available. Otherwise nil.
|
||||
/// - Warning: In certain scenarios, this will return the `faviconTemplateImage`.
|
||||
func thumbnailAttachment(for article: Article, webFeed: WebFeed) -> UNNotificationAttachment? {
|
||||
if let imageURL = article.iconImageUrl(webFeed: webFeed) {
|
||||
let thumbnail = try? UNNotificationAttachment(identifier: webFeed.webFeedID, url: imageURL, options: nil)
|
||||
return thumbnail
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerCategoriesAndActions() {
|
||||
let readAction = UNNotificationAction(identifier: "MARK_AS_READ", title: NSLocalizedString("Mark as Read", comment: "Mark as Read"), options: [])
|
||||
let starredAction = UNNotificationAction(identifier: "MARK_AS_STARRED", title: NSLocalizedString("Mark as Starred", comment: "Mark as Starred"), options: [])
|
||||
let openAction = UNNotificationAction(identifier: "OPEN_ARTICLE", title: NSLocalizedString("Open", comment: "Open"), options: [.foreground])
|
||||
|
||||
let newArticleCategory =
|
||||
UNNotificationCategory(identifier: "NEW_ARTICLE_NOTIFICATION_CATEGORY",
|
||||
actions: [openAction, readAction, starredAction],
|
||||
intentIdentifiers: [],
|
||||
hiddenPreviewsBodyPlaceholder: "",
|
||||
options: .customDismissAction)
|
||||
|
||||
UNUserNotificationCenter.current().setNotificationCategories([newArticleCategory])
|
||||
}
|
||||
|
||||
}
|
||||
|
26
Widget/Shared Views/SizeCategories.swift
Normal file
26
Widget/Shared Views/SizeCategories.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// SizeCategories.swift
|
||||
// NetNewsWire iOS Widget Extension
|
||||
//
|
||||
// Created by Stuart Breckenridge on 24/12/2020.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SizeCategories {
|
||||
|
||||
let largeSizeCategories: [ContentSizeCategory] = [.extraExtraLarge,
|
||||
.extraExtraExtraLarge,
|
||||
.accessibilityMedium,
|
||||
.accessibilityLarge,
|
||||
.accessibilityExtraLarge,
|
||||
.accessibilityExtraExtraLarge,
|
||||
.accessibilityExtraExtraExtraLarge]
|
||||
|
||||
|
||||
func isSizeCategoryLarge(category: ContentSizeCategory) -> Bool {
|
||||
largeSizeCategories.filter{ $0 == category }.count == 1
|
||||
}
|
||||
|
||||
}
|
@ -12,6 +12,7 @@ import SwiftUI
|
||||
struct StarredWidgetView : View {
|
||||
|
||||
@Environment(\.widgetFamily) var family: WidgetFamily
|
||||
@Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
|
||||
|
||||
var entry: Provider.Entry
|
||||
|
||||
@ -23,7 +24,7 @@ struct StarredWidgetView : View {
|
||||
else {
|
||||
GeometryReader { metrics in
|
||||
HStack(alignment: .top, spacing: 4) {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: -4) {
|
||||
starredImage
|
||||
Spacer()
|
||||
Text(L10n.localizedCount(entry.widgetData.currentStarredCount)).bold().font(.callout).minimumScaleFactor(0.5).lineLimit(1)
|
||||
@ -58,16 +59,21 @@ struct StarredWidgetView : View {
|
||||
var starredImage: some View {
|
||||
Image(systemName: "star.fill")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.cornerRadius(4)
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
|
||||
func maxCount() -> Int {
|
||||
if family == .systemLarge {
|
||||
return entry.widgetData.currentStarredCount > 7 ? 7 : entry.widgetData.currentStarredCount
|
||||
var reduceAccessibilityCount: Int = 0
|
||||
if SizeCategories().isSizeCategoryLarge(category: sizeCategory) {
|
||||
reduceAccessibilityCount = 1
|
||||
}
|
||||
return entry.widgetData.currentStarredCount > 3 ? 3 : entry.widgetData.currentStarredCount
|
||||
|
||||
if family == .systemLarge {
|
||||
return entry.widgetData.currentStarredCount >= 7 ? (7 - reduceAccessibilityCount) : entry.widgetData.currentStarredCount
|
||||
}
|
||||
return entry.widgetData.currentStarredCount >= 3 ? (3 - reduceAccessibilityCount) : entry.widgetData.currentStarredCount
|
||||
}
|
||||
|
||||
var inboxZero: some View {
|
||||
|
@ -12,6 +12,7 @@ import SwiftUI
|
||||
struct TodayWidgetView : View {
|
||||
|
||||
@Environment(\.widgetFamily) var family: WidgetFamily
|
||||
@Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
|
||||
|
||||
var entry: Provider.Entry
|
||||
|
||||
@ -23,7 +24,7 @@ struct TodayWidgetView : View {
|
||||
else {
|
||||
GeometryReader { metrics in
|
||||
HStack(alignment: .top, spacing: 4) {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: -4) {
|
||||
todayImage
|
||||
Spacer()
|
||||
Text(L10n.localizedCount(entry.widgetData.currentTodayCount)).bold().font(.callout).minimumScaleFactor(0.5).lineLimit(1)
|
||||
@ -57,16 +58,21 @@ struct TodayWidgetView : View {
|
||||
var todayImage: some View {
|
||||
Image(systemName: "sun.max.fill")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.cornerRadius(4)
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
|
||||
func maxCount() -> Int {
|
||||
if family == .systemLarge {
|
||||
return entry.widgetData.todayArticles.count > 7 ? 7 : entry.widgetData.todayArticles.count
|
||||
var reduceAccessibilityCount: Int = 0
|
||||
if SizeCategories().isSizeCategoryLarge(category: sizeCategory) {
|
||||
reduceAccessibilityCount = 1
|
||||
}
|
||||
return entry.widgetData.todayArticles.count > 3 ? 3 : entry.widgetData.todayArticles.count
|
||||
|
||||
if family == .systemLarge {
|
||||
return entry.widgetData.todayArticles.count >= 7 ? (7 - reduceAccessibilityCount) : entry.widgetData.todayArticles.count
|
||||
}
|
||||
return entry.widgetData.todayArticles.count >= 3 ? (3 - reduceAccessibilityCount) : entry.widgetData.todayArticles.count
|
||||
}
|
||||
|
||||
var inboxZero: some View {
|
||||
|
@ -12,6 +12,7 @@ import SwiftUI
|
||||
struct UnreadWidgetView : View {
|
||||
|
||||
@Environment(\.widgetFamily) var family: WidgetFamily
|
||||
@Environment(\.sizeCategory) var sizeCategory: ContentSizeCategory
|
||||
|
||||
var entry: Provider.Entry
|
||||
|
||||
@ -23,7 +24,7 @@ struct UnreadWidgetView : View {
|
||||
else {
|
||||
GeometryReader { metrics in
|
||||
HStack(alignment: .top, spacing: 4) {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: -4) {
|
||||
unreadImage
|
||||
Spacer()
|
||||
Text(L10n.localizedCount(entry.widgetData.currentUnreadCount)).bold().font(.callout).minimumScaleFactor(0.5).lineLimit(1)
|
||||
@ -57,16 +58,21 @@ struct UnreadWidgetView : View {
|
||||
var unreadImage: some View {
|
||||
Image(systemName: "largecircle.fill.circle")
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.frame(width: 30, height: 30, alignment: .center)
|
||||
.cornerRadius(4)
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
|
||||
func maxCount() -> Int {
|
||||
if family == .systemLarge {
|
||||
return entry.widgetData.unreadArticles.count > 7 ? 7 : entry.widgetData.unreadArticles.count
|
||||
var reduceAccessibilityCount: Int = 0
|
||||
if SizeCategories().isSizeCategoryLarge(category: sizeCategory) {
|
||||
reduceAccessibilityCount = 1
|
||||
}
|
||||
return entry.widgetData.unreadArticles.count > 3 ? 3 : entry.widgetData.unreadArticles.count
|
||||
|
||||
if family == .systemLarge {
|
||||
return entry.widgetData.unreadArticles.count >= 7 ? (7 - reduceAccessibilityCount) : entry.widgetData.unreadArticles.count
|
||||
}
|
||||
return entry.widgetData.unreadArticles.count >= 3 ? (3 - reduceAccessibilityCount) : entry.widgetData.unreadArticles.count
|
||||
}
|
||||
|
||||
var inboxZero: some View {
|
||||
|
@ -191,10 +191,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
defer { completionHandler() }
|
||||
|
||||
if let sceneDelegate = response.targetScene?.delegate as? SceneDelegate {
|
||||
sceneDelegate.handle(response)
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
|
||||
switch response.actionIdentifier {
|
||||
case "MARK_AS_READ":
|
||||
handleMarkAsRead(userInfo: userInfo)
|
||||
case "MARK_AS_STARRED":
|
||||
handleMarkAsStarred(userInfo: userInfo)
|
||||
default:
|
||||
if let sceneDelegate = response.targetScene?.delegate as? SceneDelegate {
|
||||
sceneDelegate.handle(response)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -397,3 +406,67 @@ private extension AppDelegate {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle Notification Actions
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
self.prepareAccountsForBackground()
|
||||
account!.syncArticleStatus(completion: { [weak self] _ in
|
||||
if !AccountManager.shared.isSuspended {
|
||||
if #available(iOS 14, *) {
|
||||
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()
|
||||
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: .starred, flag: true)
|
||||
account!.syncArticleStatus(completion: { [weak self] _ in
|
||||
if !AccountManager.shared.isSuspended {
|
||||
if #available(iOS 14, *) {
|
||||
try? WidgetDataEncoder.shared.encodeWidgetData()
|
||||
}
|
||||
self?.prepareAccountsForBackground()
|
||||
self?.suspendApplication()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -227,8 +227,8 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Always Show Reader View" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bf4-3X-Rfr">
|
||||
<rect key="frame" x="24" y="11.5" width="199" height="21"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Always Use Reader View" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bf4-3X-Rfr">
|
||||
<rect key="frame" x="24" y="11.5" width="187" height="21"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
@ -38,6 +38,7 @@
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>feed</string>
|
||||
<string>feeds</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
|
@ -100,58 +100,68 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
// Handle Opening of URLs
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||
func scene(_ scene: UIScene, openURLContexts urlContexts: Set<UIOpenURLContext>) {
|
||||
guard let context = urlContexts.first else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
for context in URLContexts {
|
||||
// Show Unread View or Article
|
||||
if context.url.absoluteString.contains(WidgetDeepLink.unread.url.absoluteString) {
|
||||
guard let comps = URLComponents(string: context.url.absoluteString ) else { return }
|
||||
let id = comps.queryItems?.first(where: { $0.name == "id" })?.value
|
||||
if id != nil {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
self.coordinator.selectAllUnreadFeed() {
|
||||
self.coordinator.selectArticleInCurrentFeed(id!)
|
||||
}
|
||||
} else {
|
||||
self.coordinator.selectAllUnreadFeed()
|
||||
}
|
||||
let urlString = context.url.absoluteString
|
||||
|
||||
// Handle the feed: and feeds: schemes
|
||||
if urlString.starts(with: "feed:") || urlString.starts(with: "feeds:") {
|
||||
let normalizedURLString = urlString.normalizedURL
|
||||
if normalizedURLString.mayBeURL {
|
||||
self.coordinator.showAddWebFeed(initialFeed: normalizedURLString, initialFeedName: nil)
|
||||
}
|
||||
|
||||
// Show Today View or Article
|
||||
if context.url.absoluteString.contains(WidgetDeepLink.today.url.absoluteString) {
|
||||
guard let comps = URLComponents(string: context.url.absoluteString ) else { return }
|
||||
let id = comps.queryItems?.first(where: { $0.name == "id" })?.value
|
||||
if id != nil {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
self.coordinator.selectTodayFeed() {
|
||||
self.coordinator.selectArticleInCurrentFeed(id!)
|
||||
}
|
||||
} else {
|
||||
self.coordinator.selectTodayFeed()
|
||||
}
|
||||
}
|
||||
|
||||
// Show Starred View or Article
|
||||
if context.url.absoluteString.contains(WidgetDeepLink.starred.url.absoluteString) {
|
||||
guard let comps = URLComponents(string: context.url.absoluteString ) else { return }
|
||||
let id = comps.queryItems?.first(where: { $0.name == "id" })?.value
|
||||
if id != nil {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
self.coordinator.selectStarredFeed() {
|
||||
self.coordinator.selectArticleInCurrentFeed(id!)
|
||||
}
|
||||
} else {
|
||||
self.coordinator.selectStarredFeed()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Show Unread View or Article
|
||||
if urlString.contains(WidgetDeepLink.unread.url.absoluteString) {
|
||||
guard let comps = URLComponents(string: urlString ) else { return }
|
||||
let id = comps.queryItems?.first(where: { $0.name == "id" })?.value
|
||||
if id != nil {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
self.coordinator.selectAllUnreadFeed() {
|
||||
self.coordinator.selectArticleInCurrentFeed(id!)
|
||||
}
|
||||
} else {
|
||||
self.coordinator.selectAllUnreadFeed()
|
||||
}
|
||||
}
|
||||
|
||||
// Show Today View or Article
|
||||
if urlString.contains(WidgetDeepLink.today.url.absoluteString) {
|
||||
guard let comps = URLComponents(string: urlString ) else { return }
|
||||
let id = comps.queryItems?.first(where: { $0.name == "id" })?.value
|
||||
if id != nil {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
self.coordinator.selectTodayFeed() {
|
||||
self.coordinator.selectArticleInCurrentFeed(id!)
|
||||
}
|
||||
} else {
|
||||
self.coordinator.selectTodayFeed()
|
||||
}
|
||||
}
|
||||
|
||||
// Show Starred View or Article
|
||||
if urlString.contains(WidgetDeepLink.starred.url.absoluteString) {
|
||||
guard let comps = URLComponents(string: urlString ) else { return }
|
||||
let id = comps.queryItems?.first(where: { $0.name == "id" })?.value
|
||||
if id != nil {
|
||||
if AccountManager.shared.isSuspended {
|
||||
AccountManager.shared.resumeAll()
|
||||
}
|
||||
self.coordinator.selectStarredFeed() {
|
||||
self.coordinator.selectArticleInCurrentFeed(id!)
|
||||
}
|
||||
} else {
|
||||
self.coordinator.selectStarredFeed()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user