Merge pull request #2575 from stuartbreckenridge/feature/widget-bundle

Widget Bundle
This commit is contained in:
Maurice Parker 2020-11-19 04:26:17 -06:00 committed by GitHub
commit 686ae15796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1718 additions and 11 deletions

View File

@ -31,6 +31,26 @@
173A642C2547BE9600267F6E /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; };
175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
176813D02564BA5900D98635 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813B62564B9F800D98635 /* WidgetData.swift */; };
176813D12564BA5900D98635 /* WidgetDataDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813C92564BA5400D98635 /* WidgetDataDecoder.swift */; };
176813D22564BA5900D98635 /* WidgetDataEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813BD2564BA2800D98635 /* WidgetDataEncoder.swift */; };
176813E92564BAE200D98635 /* WidgetDeepLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813D82564BA8700D98635 /* WidgetDeepLinks.swift */; };
176813F52564BB2C00D98635 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 176813F42564BB2C00D98635 /* WidgetKit.framework */; };
176813F72564BB2C00D98635 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 176813F62564BB2C00D98635 /* SwiftUI.framework */; };
176813FC2564BB2D00D98635 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 176813FB2564BB2D00D98635 /* Assets.xcassets */; };
176814002564BB2D00D98635 /* NetNewsWire iOS Widget Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1768140B2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */; };
176814132564BC8A00D98635 /* WidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176814122564BC8A00D98635 /* WidgetBundle.swift */; };
1768142D2564BCA800D98635 /* TimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1768142C2564BCA800D98635 /* TimelineProvider.swift */; };
176814362564BCC000D98635 /* UnreadWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176814352564BCC000D98635 /* UnreadWidget.swift */; };
1768143E2564BCC800D98635 /* TodayWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1768143D2564BCC800D98635 /* TodayWidget.swift */; };
176814462564BCD200D98635 /* StarredWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176814452564BCD200D98635 /* StarredWidget.swift */; };
1768144E2564BCE000D98635 /* SmartFeedSummaryWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1768144D2564BCE000D98635 /* SmartFeedSummaryWidget.swift */; };
176814572564BD0600D98635 /* ArticleItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176814562564BD0600D98635 /* ArticleItemView.swift */; };
1768145E2564BD7B00D98635 /* WidgetDataDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813C92564BA5400D98635 /* WidgetDataDecoder.swift */; };
176814652564BD7F00D98635 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813B62564B9F800D98635 /* WidgetData.swift */; };
1768146C2564BD8100D98635 /* WidgetDeepLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813D82564BA8700D98635 /* WidgetDeepLinks.swift */; };
1768147B2564BE5400D98635 /* widget-sample.json in Resources */ = {isa = PBXBuildFile; fileRef = 1768147A2564BE5400D98635 /* widget-sample.json */; };
1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */; };
1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32424BC5A65000E1E8E /* AddAccountView.swift */; };
1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */; };
@ -65,6 +85,7 @@
17A1598624E3DEDD005DA32A /* RSDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598424E3DEDD005DA32A /* RSDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
17A1598824E3DEDD005DA32A /* RSParser in Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598724E3DEDD005DA32A /* RSParser */; };
17A1598924E3DEDD005DA32A /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598724E3DEDD005DA32A /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */; };
17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; };
17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; };
17D5F17124B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; };
@ -1079,6 +1100,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
176813FE2564BB2D00D98635 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 176813F22564BB2C00D98635;
remoteInfo = "NetNewsWire WidgetExtension";
};
510C416724E5CDE3008226FD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */;
@ -1307,6 +1335,7 @@
dstSubfolderSpec = 13;
files = (
513C5CF0232571C2003D4054 /* NetNewsWire iOS Share Extension.appex in Embed App Extensions */,
176814002564BB2D00D98635 /* NetNewsWire iOS Widget Extension.appex in Embed App Extensions */,
5131463E235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
@ -1458,6 +1487,25 @@
1729529624AA1CD000D65E66 /* MacPreferencePanes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacPreferencePanes.swift; sourceTree = "<group>"; };
1729529A24AA1FD200D65E66 /* MacSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacSearchField.swift; sourceTree = "<group>"; };
173A64162547BE0900267F6E /* AccountType+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountType+Helpers.swift"; sourceTree = "<group>"; };
176813B62564B9F800D98635 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = "<group>"; };
176813BD2564BA2800D98635 /* WidgetDataEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataEncoder.swift; sourceTree = "<group>"; };
176813C92564BA5400D98635 /* WidgetDataDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataDecoder.swift; sourceTree = "<group>"; };
176813D82564BA8700D98635 /* WidgetDeepLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDeepLinks.swift; sourceTree = "<group>"; };
176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire iOS Widget Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
176813F42564BB2C00D98635 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
176813F62564BB2C00D98635 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
176813FB2564BB2D00D98635 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
176813FD2564BB2D00D98635 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSwidgetextension_target.xcconfig; sourceTree = "<group>"; };
176814122564BC8A00D98635 /* WidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBundle.swift; sourceTree = "<group>"; };
1768142C2564BCA800D98635 /* TimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProvider.swift; sourceTree = "<group>"; };
176814352564BCC000D98635 /* UnreadWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadWidget.swift; sourceTree = "<group>"; };
1768143D2564BCC800D98635 /* TodayWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayWidget.swift; sourceTree = "<group>"; };
176814452564BCD200D98635 /* StarredWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarredWidget.swift; sourceTree = "<group>"; };
1768144D2564BCE000D98635 /* SmartFeedSummaryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedSummaryWidget.swift; sourceTree = "<group>"; };
176814562564BD0600D98635 /* ArticleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleItemView.swift; sourceTree = "<group>"; };
1768147A2564BE5400D98635 /* widget-sample.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "widget-sample.json"; sourceTree = "<group>"; };
176814822564C02A00D98635 /* NetNewsWire_iOS_WidgetExtension.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_WidgetExtension.entitlements; sourceTree = "<group>"; };
1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsPreferencesModel.swift; sourceTree = "<group>"; };
1769E32424BC5A65000E1E8E /* AddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountView.swift; sourceTree = "<group>"; };
1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountModel.swift; sourceTree = "<group>"; };
@ -1476,6 +1524,7 @@
1799E6CC24C320D600511E91 /* InspectorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorModel.swift; sourceTree = "<group>"; };
179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = "<group>"; };
17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLayoutView.swift; sourceTree = "<group>"; };
17D0682B2564F47E00C0B37E /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = "<group>"; };
17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedModel.swift; sourceTree = "<group>"; };
17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModel.swift; sourceTree = "<group>"; };
17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesModel.swift; sourceTree = "<group>"; };
@ -2021,6 +2070,15 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
176813F02564BB2C00D98635 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
176813F72564BB2C00D98635 /* SwiftUI.framework in Frameworks */,
176813F52564BB2C00D98635 /* WidgetKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
510C415924E5CDE3008226FD /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -2196,6 +2254,61 @@
path = "Preference Panes";
sourceTree = "<group>";
};
176813A22564B9D100D98635 /* Widget */ = {
isa = PBXGroup;
children = (
176813B62564B9F800D98635 /* WidgetData.swift */,
176813C92564BA5400D98635 /* WidgetDataDecoder.swift */,
176813BD2564BA2800D98635 /* WidgetDataEncoder.swift */,
176813D82564BA8700D98635 /* WidgetDeepLinks.swift */,
);
path = Widget;
sourceTree = "<group>";
};
176813F82564BB2C00D98635 /* Widget */ = {
isa = PBXGroup;
children = (
17D0683F2564F7AD00C0B37E /* Helpers */,
176814822564C02A00D98635 /* NetNewsWire_iOS_WidgetExtension.entitlements */,
176814122564BC8A00D98635 /* WidgetBundle.swift */,
1768142C2564BCA800D98635 /* TimelineProvider.swift */,
176814342564BCB500D98635 /* Widget Views */,
176814552564BCED00D98635 /* Shared Views */,
176814792564BE3C00D98635 /* Resources */,
176813FB2564BB2D00D98635 /* Assets.xcassets */,
176813FD2564BB2D00D98635 /* Info.plist */,
17D0682B2564F47E00C0B37E /* Localizable.stringsdict */,
);
path = Widget;
sourceTree = "<group>";
};
176814342564BCB500D98635 /* Widget Views */ = {
isa = PBXGroup;
children = (
176814352564BCC000D98635 /* UnreadWidget.swift */,
1768143D2564BCC800D98635 /* TodayWidget.swift */,
176814452564BCD200D98635 /* StarredWidget.swift */,
1768144D2564BCE000D98635 /* SmartFeedSummaryWidget.swift */,
);
path = "Widget Views";
sourceTree = "<group>";
};
176814552564BCED00D98635 /* Shared Views */ = {
isa = PBXGroup;
children = (
176814562564BD0600D98635 /* ArticleItemView.swift */,
);
path = "Shared Views";
sourceTree = "<group>";
};
176814792564BE3C00D98635 /* Resources */ = {
isa = PBXGroup;
children = (
1768147A2564BE5400D98635 /* widget-sample.json */,
);
path = Resources;
sourceTree = "<group>";
};
1769E2FD24BC589E000E1E8E /* General */ = {
isa = PBXGroup;
children = (
@ -2276,6 +2389,13 @@
path = Add;
sourceTree = "<group>";
};
17D0683F2564F7AD00C0B37E /* Helpers */ = {
isa = PBXGroup;
children = (
);
path = Helpers;
sourceTree = "<group>";
};
510289CE2451BA1E00426DDF /* Twitter */ = {
isa = PBXGroup;
children = (
@ -2848,6 +2968,8 @@
51E4DAEC2425F6940091EB5B /* CloudKit.framework */,
51E4989624A8065700B667CB /* CloudKit.framework */,
51C452B32265141B00C03939 /* WebKit.framework */,
176813F42564BB2C00D98635 /* WidgetKit.framework */,
176813F62564BB2C00D98635 /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -3199,6 +3321,7 @@
84C9FC6522629B3900D921D6 /* Mac */,
84C9FC922262A0E600D921D6 /* iOS */,
51C0519224A77E3500194D5E /* Multiplatform */,
176813F82564BB2C00D98635 /* Widget */,
84C9FC6822629C9A00D921D6 /* Shared */,
84C9FCA52262A1E600D921D6 /* Tests */,
D5907CDA2002F084005947E5 /* xcconfig */,
@ -3228,6 +3351,7 @@
51C0513D24A77DF800194D5E /* NetNewsWire.app */,
51C0514424A77DF800194D5E /* NetNewsWire.app */,
510C415C24E5CDE3008226FD /* NetNewsWire Share Extension.appex */,
176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */,
);
name = Products;
sourceTree = "<group>";
@ -3303,6 +3427,7 @@
849A97561ED9EB0D007D329B /* Extensions */,
510C43F5243D0325009F70C3 /* ExtensionPoints */,
511D43CE231FA51100FB1562 /* Resources */,
176813A22564B9D100D98635 /* Widget */,
173A64162547BE0900267F6E /* AccountType+Helpers.swift */,
);
path = Shared;
@ -3534,6 +3659,7 @@
51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */,
51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */,
515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */,
1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */,
518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */,
65ED40F2235DF5E00081F399 /* NetNewsWire_macapp_target_macappstore.xcconfig */,
D5907CE02002F0FA005947E5 /* NetNewsWire_macapp_target.xcconfig */,
@ -3571,6 +3697,23 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
176813F22564BB2C00D98635 /* NetNewsWire iOS Widget Extension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 176814012564BB2D00D98635 /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Widget Extension" */;
buildPhases = (
176813EF2564BB2C00D98635 /* Sources */,
176813F02564BB2C00D98635 /* Frameworks */,
176813F12564BB2C00D98635 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "NetNewsWire iOS Widget Extension";
productName = "NetNewsWire WidgetExtension";
productReference = 176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
510C415B24E5CDE3008226FD /* NetNewsWire Share Extension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 510C417A24E5CDE3008226FD /* Build configuration list for PBXNativeTarget "NetNewsWire Share Extension" */;
@ -3777,6 +3920,7 @@
dependencies = (
5138E94324D3404300AFF0FE /* PBXTargetDependency */,
5131463D235A7BBE00387FDC /* PBXTargetDependency */,
176813FF2564BB2D00D98635 /* PBXTargetDependency */,
);
name = "NetNewsWire-iOS";
packageProductDependencies = (
@ -3856,10 +4000,13 @@
849C64581ED37A5D003D8FC0 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1200;
LastSwiftUpdateCheck = 1220;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "Ranchero Software";
TargetAttributes = {
176813F22564BB2C00D98635 = {
CreatedOnToolsVersion = 12.2;
};
510C415B24E5CDE3008226FD = {
CreatedOnToolsVersion = 12.0;
};
@ -3965,6 +4112,7 @@
65ED4090235DEF770081F399 /* Subscribe to Feed MAS */,
513C5CE5232571C2003D4054 /* NetNewsWire iOS Share Extension */,
51314636235A7BBE00387FDC /* NetNewsWire iOS Intents Extension */,
176813F22564BB2C00D98635 /* NetNewsWire iOS Widget Extension */,
518B2ED12351B3DD00400001 /* NetNewsWire-iOSTests */,
51C0513C24A77DF800194D5E /* Multiplatform iOS */,
51C0514324A77DF800194D5E /* Multiplatform macOS */,
@ -4089,6 +4237,16 @@
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
176813F12564BB2C00D98635 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
176813FC2564BB2D00D98635 /* Assets.xcassets in Resources */,
1768147B2564BE5400D98635 /* widget-sample.json in Resources */,
17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
510C415A24E5CDE3008226FD /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -4246,6 +4404,7 @@
516A093723609A3600EAE89B /* SettingsComboTableViewCell.xib in Resources */,
51F85BF32272531500C787DC /* Dedication.rtf in Resources */,
516A09422361248000EAE89B /* Inspector.storyboard in Resources */,
1768140B2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig in Resources */,
5103A9B424216A4200410853 /* blank.html in Resources */,
84C9FCA42262A1B800D921D6 /* LaunchScreenPhone.storyboard in Resources */,
51F85BEB22724CB600C787DC /* About.rtf in Resources */,
@ -4328,7 +4487,7 @@
/* Begin PBXShellScriptBuildPhase section */
515D50802326D02600EE1167 /* Run Script: Verify No Build Settings */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
buildActionMask = 8;
files = (
);
inputFileListPaths = (
@ -4340,7 +4499,7 @@
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
runOnlyForDeploymentPostprocessing = 1;
shellPath = /bin/sh;
shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n\n\nif [ $? -ne 0 ]\nthen\n echo \"error: Build Setting were found in the project.pbxproj file. Most likely you didn't intend to change this file and should revert it.\"\n exit 1\nfi\n";
};
@ -4429,6 +4588,23 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
176813EF2564BB2C00D98635 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
176814362564BCC000D98635 /* UnreadWidget.swift in Sources */,
1768146C2564BD8100D98635 /* WidgetDeepLinks.swift in Sources */,
1768143E2564BCC800D98635 /* TodayWidget.swift in Sources */,
1768142D2564BCA800D98635 /* TimelineProvider.swift in Sources */,
176814652564BD7F00D98635 /* WidgetData.swift in Sources */,
1768145E2564BD7B00D98635 /* WidgetDataDecoder.swift in Sources */,
176814132564BC8A00D98635 /* WidgetBundle.swift in Sources */,
176814462564BCD200D98635 /* StarredWidget.swift in Sources */,
176814572564BD0600D98635 /* ArticleItemView.swift in Sources */,
1768144E2564BCE000D98635 /* SmartFeedSummaryWidget.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
510C415824E5CDE3008226FD /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -5017,10 +5193,13 @@
51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */,
51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */,
516A09392360A2AE00EAE89B /* SettingsComboTableViewCell.swift in Sources */,
176813D22564BA5900D98635 /* WidgetDataEncoder.swift in Sources */,
3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */,
51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */,
51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */,
51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */,
176813D12564BA5900D98635 /* WidgetDataDecoder.swift in Sources */,
176813D02564BA5900D98635 /* WidgetData.swift in Sources */,
510289CD24519A1D00426DDF /* SelectComboTableViewCell.swift in Sources */,
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */,
51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */,
@ -5049,6 +5228,7 @@
51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */,
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,
51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */,
176813E92564BAE200D98635 /* WidgetDeepLinks.swift in Sources */,
512392C424E3451400F11704 /* TwitterSelectTypeTableViewController.swift in Sources */,
51B5C87723F22B8200032075 /* ExtensionContainers.swift in Sources */,
51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */,
@ -5365,6 +5545,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
176813FF2564BB2D00D98635 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 176813F22564BB2C00D98635 /* NetNewsWire iOS Widget Extension */;
targetProxy = 176813FE2564BB2D00D98635 /* PBXContainerItemProxy */;
};
510C416824E5CDE3008226FD /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 510C415B24E5CDE3008226FD /* NetNewsWire Share Extension */;
@ -5552,6 +5737,152 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
176814022564BB2D00D98635 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = Widget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.stuartbreckenridge.opensource.NetNewsWire.iOS.NetNewsWire-Widget";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
176814032564BB2D00D98635 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = Widget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.stuartbreckenridge.opensource.NetNewsWire.iOS.NetNewsWire-Widget";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
510C416A24E5CDE3008226FD /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 510C418724E5D2E3008226FD /* NetNewsWire_shareextension_target.xcconfig */;
@ -5686,6 +6017,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
};
name = Debug;
};
@ -5693,6 +6025,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
};
name = Release;
};
@ -5741,6 +6074,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
176814012564BB2D00D98635 /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Widget Extension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
176814022564BB2D00D98635 /* Debug */,
176814032564BB2D00D98635 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
510C417A24E5CDE3008226FD /* Build configuration list for PBXNativeTarget "NetNewsWire Share Extension" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -0,0 +1,32 @@
//
// WidgetData.swift
// NetNewsWire
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
struct WidgetData: Codable {
let currentUnreadCount: Int
let currentTodayCount: Int
let currentStarredCount: Int
let unreadArticles: [LatestArticle]
let starredArticles: [LatestArticle]
let todayArticles: [LatestArticle]
let lastUpdateTime: Date
}
struct LatestArticle: Codable, Identifiable {
var id: String
let feedTitle: String
let articleTitle: String?
let articleSummary: String?
let feedIcon: Data? // Base64 encoded image data
let pubDate: String
}

View File

@ -0,0 +1,36 @@
//
// WidgetDataDecoder.swift
// NetNewsWire
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
struct WidgetDataDecoder {
static func decodeWidgetData() throws -> WidgetData {
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
let dataURL = containerURL?.appendingPathComponent("widget-data.json")
if FileManager.default.fileExists(atPath: dataURL!.path) {
let decodedWidgetData = try JSONDecoder().decode(WidgetData.self, from: Data(contentsOf: dataURL!))
return decodedWidgetData
} else {
return WidgetData(currentUnreadCount: 0, currentTodayCount: 0, currentStarredCount: 0, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date())
}
}
static func sampleData() -> WidgetData {
let pathToSample = Bundle.main.url(forResource: "widget-sample", withExtension: "json")
do {
let data = try Data(contentsOf: pathToSample!)
let decoded = try JSONDecoder().decode(WidgetData.self, from: data)
return decoded
} catch {
return WidgetData(currentUnreadCount: 0, currentTodayCount: 0, currentStarredCount: 0, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date())
}
}
}

View File

@ -0,0 +1,91 @@
//
// WidgetDataEncoder.swift
// NetNewsWire
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
import WidgetKit
import os.log
import UIKit
import RSCore
import Articles
@available(iOS 14, *)
struct WidgetDataEncoder {
static let taskIdentifier = "com.ranchero.NetNewsWire.WidgetEncode"
static func encodeWidgetData() {
os_log(.info, "Starting widget data encoding")
do {
// Unread Articles
let unreadArticles = try SmartFeedsController.shared.unreadFeed.fetchArticles().sorted(by: { $0.datePublished! > $1.datePublished! })
var unread = [LatestArticle]()
for article in unreadArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? article.contentHTML?.strippingHTML().trimmingWhitespace : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished!.description)
unread.append(latestArticle)
if unread.count == 8 { break }
}
// Starred Articles
let starredArticles = try SmartFeedsController.shared.starredFeed.fetchArticles().sorted(by: { $0.datePublished! > $1.datePublished! })
var starred = [LatestArticle]()
for article in starredArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? article.contentHTML?.strippingHTML().trimmingWhitespace : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished!.description)
starred.append(latestArticle)
if starred.count == 8 { break }
}
// Today Articles
let todayArticles = try SmartFeedsController.shared.todayFeed.fetchArticles().sorted(by: { $0.datePublished! > $1.datePublished! })
var today = [LatestArticle]()
for article in todayArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? article.contentHTML?.strippingHTML().trimmingWhitespace : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIcon: article.iconImage()?.image.dataRepresentation(),
pubDate: article.datePublished!.description)
today.append(latestArticle)
if today.count == 8 { break }
}
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
currentTodayCount: try! SmartFeedsController.shared.todayFeed.fetchArticles().count,
currentStarredCount: try! SmartFeedsController.shared.starredFeed.fetchArticles().count,
unreadArticles: unread,
starredArticles: starred,
todayArticles:today,
lastUpdateTime: Date())
let encodedData = try JSONEncoder().encode(latestData)
let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
let dataURL = containerURL?.appendingPathComponent("widget-data.json")
if FileManager.default.fileExists(atPath: dataURL!.path) {
try FileManager.default.removeItem(at: dataURL!)
}
try encodedData.write(to: dataURL!)
WidgetCenter.shared.reloadAllTimelines()
os_log(.info, "Finished encoding widget data")
} catch {
os_log(.error, "%@", error.localizedDescription)
}
}
}

View File

@ -0,0 +1,46 @@
//
// WidgetDeepLinks.swift
// NetNewsWire
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
enum WidgetDeepLink {
case unread
case unreadArticle(id: String)
case today
case todayArticle(id: String)
case starred
case starredArticle(id: String)
case icon
var url: URL {
switch self {
case .unread:
return URL(string: "nnw://showunread")!
case .unreadArticle(let articleID):
var url = URLComponents(url: WidgetDeepLink.unread.url, resolvingAgainstBaseURL: false)!
url.queryItems = [URLQueryItem(name: "id", value: articleID)]
return url.url!
case .today:
return URL(string: "nnw://showtoday")!
case .todayArticle(let articleID):
var url = URLComponents(url: WidgetDeepLink.today.url, resolvingAgainstBaseURL: false)!
url.queryItems = [URLQueryItem(name: "id", value: articleID)]
return url.url!
case .starred:
return URL(string: "nnw://showstarred")!
case .starredArticle(let articleID):
var url = URLComponents(url: WidgetDeepLink.starred.url, resolvingAgainstBaseURL: false)!
url.queryItems = [URLQueryItem(name: "id", value: articleID)]
return url.url!
case .icon:
return URL(string: "nnw://icon")!
}
}
}

52
Technotes/Widgets.md Normal file
View File

@ -0,0 +1,52 @@
# Widgets on iOS
There are _currently_ seven widgets available for iOS:
- 1x small widget that displays the current count of each of the Smart Feeds
- 3x medium widgets—one for each of the smart feeds.
- 3x large widgets—bigger versions of the medium widgets
## Widget Data
The widget does not have access to the parent app's database. To surface data to the widget, a small amount of article data is encoded to JSON (see `WidgetDataEncoder`) and saved to the AppGroup container.
Widget data is written at three points:
1. After applicationDidFinishLaunching
2. As part of a background refresh
3. When the scene enters the background
The widget timeline is refreshed—via `WidgetCenter.shared.reloadAllTimelines()`—after each of the above.
## Deep Links
The medium widgets support deep links for each of the articles that are surfaced.
If the user taps on an unread article in the unread widget, the widget opens the parent app with a deep link URL (see `WidgetDeepLink`), for example: `nnw://showunread?id={articeID}`. Once the app is opened, `scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)` is called and it is then determined what should be presented to the user based on the URL. If there is no `id` parameter—the user has tapped on a small widget or a non-linked item in a medium widget—the relevant smart feed controller is displayed.
## Data Models
```swift
struct WidgetData: Codable {
let currentUnreadCount: Int
let currentTodayCount: Int
let currentStarredCount: Int
let unreadArticles: [LatestArticle]
let starredArticles: [LatestArticle]
let todayArticles: [LatestArticle]
let lastUpdateTime: Date
}
struct LatestArticle: Codable, Identifiable {
var id: String // articleID
let feedTitle: String
let articleTitle: String?
let articleSummary: String?
let feedIcon: Data? // Base64 encoded image
let pubDate: String
}
```

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.933",
"green" : "0.416",
"red" : "0.031"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.945",
"green" : "0.502",
"red" : "0.176"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,116 @@
{
"images" : [
{
"filename" : "icon-41.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "icon-60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "icon-58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "icon-80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-121.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "icon-120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "icon-180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "icon-20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "icon-42.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "icon-29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "icon-59.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "icon-81.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "icon-152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "icon-167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "icon-1024.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.130",
"green" : "0.130",
"red" : "0.130"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

37
Widget/Info.plist Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>NetNewsWire Widget</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
<key>OrganizationIdentifier</key>
<string>$(ORGANIZATION_IDENTIFIER)</string>
<key>DeveloperEntitlements</key>
<string>$(DEVELOPER_ENTITLEMENTS)</string>
<key>AppGroup</key>
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
</dict>
</plist>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UnreadCount</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@unread_count@</string>
<key>unread_count</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>u</string>
<key>zero</key>
<string>No more unread articles</string>
<key>one</key>
<string>+ 1 more unread article</string>
<key>other</key>
<string>+ %u more unread articles</string>
</dict>
</dict>
<key>StarredCount</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@starred_count@</string>
<key>starred_count</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>u</string>
<key>zero</key>
<string>No more starred articles</string>
<key>one</key>
<string>+ 1 more starred article</string>
<key>other</key>
<string>+ %u more starred articles</string>
</dict>
</dict>
<key>TodayCount</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@today_count@</string>
<key>today_count</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>u</string>
<key>zero</key>
<string>No more recent articles</string>
<key>one</key>
<string>+ 1 more recent article</string>
<key>other</key>
<string>+ %u more recent articles</string>
</dict>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
</array>
</dict>
</plist>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,69 @@
//
// ArticleItemView.swift
// NetNewsWire Widget Extension
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
struct ArticleItemView: View {
var article: LatestArticle
var deepLink: URL
var body: some View {
Link(destination: deepLink, label: {
HStack(alignment: .top, spacing: nil, content: {
// Feed Icon
Image(uiImage: thumbnail(article.feedIcon))
.resizable()
.frame(width: 25, height: 25)
.cornerRadius(4)
// Title and Feed Name
VStack(alignment: .leading) {
Text(article.articleTitle ?? "Untitled")
.font(.footnote)
.bold()
.lineLimit(1)
.foregroundColor(.primary)
.padding(.top, -3)
HStack {
Text(article.feedTitle)
.font(.caption)
.lineLimit(1)
.foregroundColor(.secondary)
Spacer()
Text(pubDate(article.pubDate))
.font(.caption)
.lineLimit(1)
.foregroundColor(.secondary)
}
}
})
}).padding(.bottom, 4)
}
func thumbnail(_ data: Data?) -> UIImage {
if data == nil {
return UIImage(systemName: "globe")!
} else {
return UIImage(data: data!)!
}
}
func pubDate(_ dateString: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let date = dateFormatter.date(from: dateString)
let displayFormatter = DateFormatter()
displayFormatter.dateStyle = .medium
displayFormatter.timeStyle = .none
return displayFormatter.string(from: date!)
}
}

View File

@ -0,0 +1,66 @@
//
// TimelineProvider.swift
// NetNewsWire Widget Extension
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> WidgetTimelineEntry {
WidgetTimelineEntry(date: Date(), widgetData: WidgetDataDecoder.sampleData())
}
func getSnapshot(in context: Context, completion: @escaping (WidgetTimelineEntry) -> Void) {
if context.isPreview {
let entry = WidgetTimelineEntry(date: Date(),
widgetData: WidgetDataDecoder.sampleData())
completion(entry)
} else {
do {
let widgetData = try WidgetDataDecoder.decodeWidgetData()
let entry = WidgetTimelineEntry(date: Date(), widgetData: widgetData)
completion(entry)
} catch {
let entry = WidgetTimelineEntry(date: Date(),
widgetData: WidgetData(currentUnreadCount: 41, currentTodayCount: 40, currentStarredCount: 12, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date()) )
completion(entry)
}
}
}
func getTimeline(in context: Context, completion: @escaping (Timeline<WidgetTimelineEntry>) -> Void) {
// Create current timeline entry for now.
let date = Date()
var entry: WidgetTimelineEntry
do {
let widgetData = try WidgetDataDecoder.decodeWidgetData()
entry = WidgetTimelineEntry(date: date, widgetData: widgetData)
} catch {
entry = WidgetTimelineEntry(date: date, widgetData: WidgetData(currentUnreadCount: 0, currentTodayCount: 0, currentStarredCount: 0, unreadArticles: [], starredArticles: [], todayArticles: [], lastUpdateTime: Date()))
}
// Configure next update in 1 hour.
let nextUpdateDate = Calendar.current.date(byAdding: .hour, value: 1, to: date)!
let timeline = Timeline(
entries:[entry],
policy: .after(nextUpdateDate))
completion(timeline)
}
public typealias Entry = WidgetTimelineEntry
}
struct WidgetTimelineEntry: TimelineEntry {
public let date: Date
public let widgetData: WidgetData
}

View File

@ -0,0 +1,102 @@
//
// SmartFeedSummaryWidget.swift
// NetNewsWire Widget Extension
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import WidgetKit
import SwiftUI
struct SmartFeedSummaryWidgetView: View {
@Environment(\.widgetFamily) var family: WidgetFamily
var entry: Provider.Entry
var body: some View {
switch family {
case .systemSmall:
smallWidget
case .systemMedium:
smallWidget
case .systemLarge:
smallWidget
@unknown default:
smallWidget
}
}
@ViewBuilder
var smallWidget: some View {
VStack(alignment: .leading) {
Link(destination: WidgetDeepLink.today.url, label: {
HStack {
todayImage
Text(formattedCount(entry.widgetData.currentTodayCount) + " Today").font(.caption)
Spacer()
}
})
Link(destination: WidgetDeepLink.unread.url, label: {
HStack {
unreadImage
Text(formattedCount(entry.widgetData.currentUnreadCount) + " Unread").font(.caption)
Spacer()
}
})
Link(destination: WidgetDeepLink.starred.url, label: {
HStack {
starredImage
Text(formattedCount(entry.widgetData.currentStarredCount) + " Starred").font(.caption)
Spacer()
}
})
Spacer()
HStack {
Spacer()
Text("Smartfeeds").bold().textCase(.uppercase).font(.caption2)
Spacer()
}
}.padding()
}
func formattedCount(_ count: Int) -> String {
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .decimal
return formatter.string(from: NSNumber(value: count))!
}
var unreadImage: some View {
Image(systemName: "largecircle.fill.circle")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(.accentColor)
}
var nnwImage: some View {
Image("CornerIcon")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
.cornerRadius(4)
}
var starredImage: some View {
Image(systemName: "star.fill")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(.yellow)
}
var todayImage: some View {
Image(systemName: "sun.max.fill")
.resizable()
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(.orange)
}
}

View File

@ -0,0 +1,109 @@
//
// StarredWidget.swift
// NetNewsWire Widget Extension
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import WidgetKit
import SwiftUI
struct StarredWidgetView : View {
@Environment(\.widgetFamily) var family: WidgetFamily
var entry: Provider.Entry
var body: some View {
if entry.widgetData.starredArticles.count == 0 {
inboxZero
}
else {
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 8) {
VStack {
starredImage
Spacer()
nnwImage
}
VStack(alignment:.leading, spacing: 2) {
ForEach(0..<maxCount(), content: { i in
ArticleItemView(article: entry.widgetData.starredArticles[i],
deepLink: WidgetDeepLink.starredArticle(id: entry.widgetData.starredArticles[i].id).url)
})
Spacer()
HStack {
Spacer()
countText
}
}
}
}
.padding()
.widgetURL(WidgetDeepLink.starred.url)
}
}
var starredImage: some View {
Image(systemName: "star.fill")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.cornerRadius(4)
.foregroundColor(.yellow)
}
var nnwImage: some View {
Image("CornerIcon")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.cornerRadius(4)
}
var countText: some View {
var count = entry.widgetData.currentStarredCount
if family == .systemLarge {
count = count - 8
} else {
count = count - 3
}
if count < 0 { count = 0 }
let formatString = NSLocalizedString("StarredCount",
comment: "Starred Count Format")
let str = String.localizedStringWithFormat(formatString, UInt(count))
return Text(str)
.font(.caption2)
.bold()
.foregroundColor(.accentColor)
}
func maxCount() -> Int {
if family == .systemLarge {
return entry.widgetData.currentStarredCount > 8 ? 8 : entry.widgetData.currentStarredCount
}
return entry.widgetData.currentStarredCount > 3 ? 3 : entry.widgetData.currentStarredCount
}
var inboxZero: some View {
VStack {
Spacer()
Text("#StarredZero")
.italic()
.font(Font.system(.subheadline, design: .serif))
Spacer()
HStack {
Image("CornerIcon")
.resizable()
.frame(width: 15, height: 15, alignment: .center)
.cornerRadius(4)
Text("You've not starred any artices.")
.font(.caption2)
.foregroundColor(.gray)
}
}.padding()
}
}

View File

@ -0,0 +1,109 @@
//
// TodayWidget.swift
// NetNewsWire Widget Extension
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import WidgetKit
import SwiftUI
struct TodayWidgetView : View {
@Environment(\.widgetFamily) var family: WidgetFamily
var entry: Provider.Entry
var body: some View {
if entry.widgetData.todayArticles.count == 0 {
inboxZero
}
else {
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 8) {
VStack {
todayImage
Spacer()
nnwImage
}
VStack(alignment:.leading, spacing: 2) {
ForEach(0..<maxCount(), content: { i in
ArticleItemView(article: entry.widgetData.todayArticles[i],
deepLink: WidgetDeepLink.todayArticle(id: entry.widgetData.todayArticles[i].id).url)
})
Spacer()
HStack {
Spacer()
countText
}
}
}
}
.padding()
.widgetURL(WidgetDeepLink.today.url)
}
}
var todayImage: some View {
Image(systemName: "sun.max.fill")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.cornerRadius(4)
.foregroundColor(.orange)
}
var nnwImage: some View {
Image("CornerIcon")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.cornerRadius(4)
}
var countText: some View {
var count = entry.widgetData.currentTodayCount
if family == .systemLarge {
count = count - 8
} else {
count = count - 3
}
if count < 0 { count = 0 }
let formatString = NSLocalizedString("TodayCount",
comment: "Today Count Format")
let str = String.localizedStringWithFormat(formatString, UInt(count))
return Text(str)
.font(.caption2)
.bold()
.foregroundColor(.accentColor)
}
func maxCount() -> Int {
if family == .systemLarge {
return entry.widgetData.todayArticles.count > 8 ? 8 : entry.widgetData.todayArticles.count
}
return entry.widgetData.todayArticles.count > 3 ? 3 : entry.widgetData.todayArticles.count
}
var inboxZero: some View {
VStack {
Spacer()
Text("#TodayZero")
.italic()
.font(Font.system(.subheadline, design: .serif))
Spacer()
HStack {
Image("CornerIcon")
.resizable()
.frame(width: 15, height: 15, alignment: .center)
.cornerRadius(4)
Text("There are no recent articles to read.")
.font(.caption2)
.foregroundColor(.gray)
}
}.padding()
}
}

View File

@ -0,0 +1,110 @@
//
// UnreadWidget.swift
// NetNewsWire Widget Extension
//
// Created by Stuart Breckenridge on 18/11/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import WidgetKit
import SwiftUI
struct UnreadWidgetView : View {
@Environment(\.widgetFamily) var family: WidgetFamily
var entry: Provider.Entry
var body: some View {
if entry.widgetData.currentUnreadCount == 0 {
inboxZero
}
else {
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 8) {
VStack {
unreadImage
Spacer()
nnwImage
}
VStack(alignment:.leading, spacing: 2) {
ForEach(0..<maxCount(), content: { i in
ArticleItemView(article: entry.widgetData.unreadArticles[i],
deepLink: WidgetDeepLink.unreadArticle(id: entry.widgetData.unreadArticles[i].id).url)
})
Spacer()
HStack {
Spacer()
countText
}
}
}
}
.padding()
.widgetURL(WidgetDeepLink.unread.url)
}
}
var unreadImage: some View {
Image(systemName: "largecircle.fill.circle")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.cornerRadius(4)
.foregroundColor(.accentColor)
}
var nnwImage: some View {
Image("CornerIcon")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.cornerRadius(4)
}
var countText: some View {
var count = entry.widgetData.currentUnreadCount
if family == .systemLarge {
count = count - 8
} else {
count = count - 3
}
if count < 0 { count = 0 }
let formatString = NSLocalizedString("UnreadCount",
comment: "Unread Count Format")
let str = String.localizedStringWithFormat(formatString, UInt(count))
return Text(str)
.font(.caption2)
.bold()
.foregroundColor(.accentColor)
}
func maxCount() -> Int {
if family == .systemLarge {
return entry.widgetData.unreadArticles.count > 8 ? 8 : entry.widgetData.unreadArticles.count
}
return entry.widgetData.unreadArticles.count > 3 ? 3 : entry.widgetData.unreadArticles.count
}
var inboxZero: some View {
VStack {
Spacer()
Text("#UnreadZero")
.italic()
.font(Font.system(.subheadline, design: .serif))
Spacer()
HStack {
Image("CornerIcon")
.resizable()
.frame(width: 15, height: 15, alignment: .center)
.cornerRadius(4)
Text("There's nothing to read right now.")
.font(.caption2)
.foregroundColor(.gray)
}
}.padding()
}
}

97
Widget/WidgetBundle.swift Normal file
View File

@ -0,0 +1,97 @@
//
// WidgetBundle.swift
// NetNewsWire Widget Extension
//
// Created by Stuart Breckenridge on 18/11/2020.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import WidgetKit
import SwiftUI
// MARK: - Supported Widgets
struct UnreadWidget: Widget {
let kind: String = "com.ranchero.NetNewsWire.UnreadWidget"
var body: some WidgetConfiguration {
return StaticConfiguration(kind: kind, provider: Provider(), content: { entry in
UnreadWidgetView(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("WidgetBackground"))
})
.configurationDisplayName("Your Unread Articles")
.description("A sneak peak at what's left unread.")
.supportedFamilies([.systemMedium, .systemLarge])
}
}
struct TodayWidget: Widget {
let kind: String = "com.ranchero.NetNewsWire.TodayWidget"
var body: some WidgetConfiguration {
return StaticConfiguration(kind: kind, provider: Provider(), content: { entry in
TodayWidgetView(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("WidgetBackground"))
})
.configurationDisplayName("Your Today Articles")
.description("A sneak peak at recently published articles.")
.supportedFamilies([.systemMedium, .systemLarge])
}
}
struct StarredWidget: Widget {
let kind: String = "com.ranchero.NetNewsWire.StarredWidget"
var body: some WidgetConfiguration {
return StaticConfiguration(kind: kind, provider: Provider(), content: { entry in
StarredWidgetView(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("WidgetBackground"))
})
.configurationDisplayName("Your Starred Articles")
.description("A sneak peak at your starred articles.")
.supportedFamilies([.systemMedium, .systemLarge])
}
}
struct SmartFeedSummaryWidget: Widget {
let kind: String = "com.ranchero.NetNewsWire.SmartFeedSummaryWidget"
var body: some WidgetConfiguration {
return StaticConfiguration(kind: kind, provider: Provider(), content: { entry in
SmartFeedSummaryWidgetView(entry: entry)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("WidgetBackground"))
})
.configurationDisplayName("Your Smart Feed Summary")
.description("A count of your smart feeds.")
.supportedFamilies([.systemSmall])
}
}
// MARK: - WidgetBundle
@main
struct NetNewsWireWidgets: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
SmartFeedSummaryWidget()
UnreadWidget()
TodayWidget()
StarredWidget()
}
}

View File

@ -13,6 +13,7 @@ import Account
import BackgroundTasks
import os.log
import Secrets
import WidgetKit
var appDelegate: AppDelegate!
@ -114,6 +115,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
syncTimer!.update()
#endif
if #available(iOS 14, *) {
WidgetDataEncoder.encodeWidgetData()
}
return true
}
@ -384,6 +390,9 @@ private extension AppDelegate {
}
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in
if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) {
WidgetDataEncoder.encodeWidgetData()
}
self.suspendApplication()
os_log("Account refresh operation completed.", log: self.log, type: .info)
task.setTaskCompleted(success: true)

View File

@ -1277,6 +1277,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
func toggleSidebar() {
rootSplitViewController.preferredDisplayMode = rootSplitViewController.displayMode == .allVisible ? .primaryHidden : .allVisible
}
func selectArticleInCurrentFeed(_ articleID: String) {
if let article = self.articles.first(where: { $0.articleID == articleID }) {
self.selectArticle(article)
}
}
}
// MARK: UISplitViewControllerDelegate
@ -2291,10 +2297,4 @@ private extension SceneCoordinator {
return false
}
func selectArticleInCurrentFeed(_ articleID: String) {
if let article = self.articles.first(where: { $0.articleID == articleID }) {
self.selectArticle(article)
}
}
}

View File

@ -23,11 +23,17 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window!.tintColor = AppAssets.primaryAccentColor
updateUserInterfaceStyle()
window!.rootViewController = coordinator.start(for: window!.frame.size)
coordinator.restoreWindowState(session.stateRestorationActivity)
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
if let _ = connectionOptions.urlContexts.first?.url {
window?.makeKeyAndVisible()
self.scene(scene, openURLContexts: connectionOptions.urlContexts)
return
}
if let shortcutItem = connectionOptions.shortcutItem {
window!.makeKeyAndVisible()
handleShortcutItem(shortcutItem)
@ -42,7 +48,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
coordinator.handle(userActivity)
}
}
window!.makeKeyAndVisible()
}
@ -59,6 +65,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
func sceneDidEnterBackground(_ scene: UIScene) {
if #available(iOS 14, *) {
WidgetDataEncoder.encodeWidgetData()
}
ArticleStringFormatter.emptyCaches()
appDelegate.prepareAccountsForBackground()
}
@ -89,6 +98,63 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
coordinator.cleanUp(conditional: conditional)
}
// Handle Opening of URLs
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
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.selectFeed(SmartFeedsController.shared.unreadFeed, completion: {
self.coordinator.selectArticleInCurrentFeed(id!)
})
} else {
self.coordinator.selectAllUnreadFeed()
}
}
// 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.selectFeed(SmartFeedsController.shared.todayFeed, completion: {
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.selectFeed(SmartFeedsController.shared.starredFeed, completion: {
self.coordinator.selectArticleInCurrentFeed(id!)
})
} else {
self.coordinator.selectStarredFeed()
}
}
}
}
}
}
private extension SceneDelegate {

View File

@ -0,0 +1,41 @@
CODE_SIGN_IDENTITY= iPhone Developer
DEVELOPMENT_TEAM = M8L2WTLA8W
CODE_SIGN_STYLE = Automatic
ORGANIZATION_IDENTIFIER = com.ranchero
PROVISIONING_PROFILE_SPECIFIER =
// developers can locally override the Xcode settings for code signing
// by creating a DeveloperSettings.xcconfig file locally at the appropriate path
// This allows a pristine project to have code signing set up with the appropriate
// developer ID and certificates, and for dev to be able to have local settings
// without needing to check in anything into source control
//
// As an example, make a ../../SharedXcodeSettings/DeveloperSettings.xcconfig file and
// give it the contents
//
// CODE_SIGN_IDENTITY[sdk=macosx*] = Mac Developer
// CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
// CODE_SIGN_IDENTITY[sdk=iphonesimulator*] = iPhone Developer
// DEVELOPMENT_TEAM = <Your Team ID>
// BUNDLE_ROOT = <Your Domain Name Reversed>
// CODE_SIGN_STYLE = Automatic
// PROVISIONING_PROFILE_SPECIFIER =
//
// And you should be able to build without code signing errors and without modifying
// the NetNewsWire Xcode project.
//
// Example: if your NetNewsWire Xcode project file is at
// /Users/Shared/git/NetNewsWire/NetNewsWire.xcodeproj
// create your DeveloperSettings.xcconfig file at
// /Users/Shared/git/SharedXcodeSettings/DeveloperSettings.xcconfig
//
#include? "../../SharedXcodeSettings/ProjectSettings.xcconfig"
#include? "../../SharedXcodeSettings/DeveloperSettings.xcconfig"
#include "./common/NetNewsWire_ios_target_common.xcconfig"
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../../Frameworks
CODE_SIGN_ENTITLEMENTS = Widget/NetNewsWire_iOS_WidgetExtension.entitlements
INFOPLIST_FILE = Widget/Info.plist
PRODUCT_BUNDLE_IDENTIFIER = $(ORGANIZATION_IDENTIFIER).NetNewsWire-Widget
PRODUCT_NAME = $(TARGET_NAME)