From a3135da6a8f2d86d07c7551ee28ae0429682af73 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 18 Nov 2020 13:42:32 +0800 Subject: [PATCH] Adds Widgets.md --- NetNewsWire.xcodeproj/project.pbxproj | 22 ++++++------ Technotes/Widgets.md | 51 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 Technotes/Widgets.md diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index bf93070b1..127657480 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -38,7 +38,7 @@ 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 Widget Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 176813F32564BB2C00D98635 /* NetNewsWire Widget Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 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 */; }; @@ -1334,7 +1334,7 @@ dstSubfolderSpec = 13; files = ( 513C5CF0232571C2003D4054 /* NetNewsWire iOS Share Extension.appex in Embed App Extensions */, - 176814002564BB2D00D98635 /* NetNewsWire Widget 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"; @@ -1490,7 +1490,7 @@ 176813BD2564BA2800D98635 /* WidgetDataEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataEncoder.swift; sourceTree = ""; }; 176813C92564BA5400D98635 /* WidgetDataDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataDecoder.swift; sourceTree = ""; }; 176813D82564BA8700D98635 /* WidgetDeepLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDeepLinks.swift; sourceTree = ""; }; - 176813F32564BB2C00D98635 /* NetNewsWire Widget Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire Widget Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; @@ -3340,7 +3340,7 @@ 51C0513D24A77DF800194D5E /* NetNewsWire.app */, 51C0514424A77DF800194D5E /* NetNewsWire.app */, 510C415C24E5CDE3008226FD /* NetNewsWire Share Extension.appex */, - 176813F32564BB2C00D98635 /* NetNewsWire Widget Extension.appex */, + 176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */, ); name = Products; sourceTree = ""; @@ -3686,9 +3686,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 176813F22564BB2C00D98635 /* NetNewsWire Widget Extension */ = { + 176813F22564BB2C00D98635 /* NetNewsWire iOS Widget Extension */ = { isa = PBXNativeTarget; - buildConfigurationList = 176814012564BB2D00D98635 /* Build configuration list for PBXNativeTarget "NetNewsWire Widget Extension" */; + buildConfigurationList = 176814012564BB2D00D98635 /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Widget Extension" */; buildPhases = ( 176813EF2564BB2C00D98635 /* Sources */, 176813F02564BB2C00D98635 /* Frameworks */, @@ -3698,9 +3698,9 @@ ); dependencies = ( ); - name = "NetNewsWire Widget Extension"; + name = "NetNewsWire iOS Widget Extension"; productName = "NetNewsWire WidgetExtension"; - productReference = 176813F32564BB2C00D98635 /* NetNewsWire Widget Extension.appex */; + productReference = 176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */; productType = "com.apple.product-type.app-extension"; }; 510C415B24E5CDE3008226FD /* NetNewsWire Share Extension */ = { @@ -4101,11 +4101,11 @@ 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 */, 510C415B24E5CDE3008226FD /* NetNewsWire Share Extension */, - 176813F22564BB2C00D98635 /* NetNewsWire Widget Extension */, ); }; /* End PBXProject section */ @@ -5535,7 +5535,7 @@ /* Begin PBXTargetDependency section */ 176813FF2564BB2D00D98635 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 176813F22564BB2C00D98635 /* NetNewsWire Widget Extension */; + target = 176813F22564BB2C00D98635 /* NetNewsWire iOS Widget Extension */; targetProxy = 176813FE2564BB2D00D98635 /* PBXContainerItemProxy */; }; 510C416824E5CDE3008226FD /* PBXTargetDependency */ = { @@ -6062,7 +6062,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 176814012564BB2D00D98635 /* Build configuration list for PBXNativeTarget "NetNewsWire Widget Extension" */ = { + 176814012564BB2D00D98635 /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Widget Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( 176814022564BB2D00D98635 /* Debug */, diff --git a/Technotes/Widgets.md b/Technotes/Widgets.md new file mode 100644 index 000000000..2c3ef4792 --- /dev/null +++ b/Technotes/Widgets.md @@ -0,0 +1,51 @@ +# Widgets on iOS + +There are _currently_ four 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. + +## 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)` is called and is then determined what should be presented to the user. If there is no `id` parameter, 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 + +} +``` + +