Compare commits
3 Commits
991a106907
...
ae0c91d719
Author | SHA1 | Date |
---|---|---|
Shadowfacts | ae0c91d719 | |
Shadowfacts | 941bc4713e | |
Shadowfacts | 168c5abbcf |
|
@ -59,6 +59,12 @@ public struct LocalData {
|
|||
public let clientSecret: String
|
||||
public let token: Token
|
||||
|
||||
/// A filename-safe string for this account
|
||||
public var persistenceKey: String {
|
||||
// slashes the base64 string turn into subdirectories which we don't want
|
||||
id.base64EncodedString().replacingOccurrences(of: "/", with: "_")
|
||||
}
|
||||
|
||||
public init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) {
|
||||
// we use a hash of instance host and account id rather than random ids so that
|
||||
// user activites can uniquely identify accounts across devices
|
||||
|
|
|
@ -27,9 +27,7 @@ public class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
|||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer")
|
||||
|
||||
public init(account: LocalData.Account) {
|
||||
// slashes the base64 string turn into subdirectories which we don't want
|
||||
let name = account.id.base64EncodedString().replacingOccurrences(of: "/", with: "_")
|
||||
super.init(name: name, managedObjectModel: PersistentContainer.managedObjectModel)
|
||||
super.init(name: account.persistenceKey, managedObjectModel: PersistentContainer.managedObjectModel)
|
||||
|
||||
let groupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.net.shadowfacts.Reader")!
|
||||
let containerAppSupportURL = groupContainerURL
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<entity name="SyncState" representedClassName="SyncState" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="lastSync" attributeType="Date" usesScalarValueType="NO"/>
|
||||
</entity>
|
||||
<fetchRequest name="FetchRequest" entity="Item" predicateString="TRUEPREDICATE"/>
|
||||
<elements>
|
||||
<element name="Feed" positionX="-54" positionY="9" width="128" height="119"/>
|
||||
<element name="Group" positionX="-63" positionY="-18" width="128" height="74"/>
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
D6C68806272CD27700874C10 /* ReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68805272CD27700874C10 /* ReaderTests.swift */; };
|
||||
D6C68810272CD27700874C10 /* ReaderUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C6880F272CD27700874C10 /* ReaderUITests.swift */; };
|
||||
D6C68812272CD27700874C10 /* ReaderUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */; };
|
||||
D6D5FA24285FE9DB00BBF188 /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D5FA23285FE9DB00BBF188 /* WidgetHelper.swift */; };
|
||||
D6D5FA27285FEA6900BBF188 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D5FA26285FEA6900BBF188 /* WidgetData.swift */; };
|
||||
D6D5FA28285FEA8A00BBF188 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D5FA26285FEA6900BBF188 /* WidgetData.swift */; };
|
||||
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2434B278B456A0005E546 /* ItemsViewController.swift */; };
|
||||
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */; };
|
||||
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24368278BABB40005E546 /* UIColor+App.swift */; };
|
||||
|
@ -39,6 +42,15 @@
|
|||
D6EEDE87285FA75D009F854E /* Fervor in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDE86285FA75D009F854E /* Fervor */; };
|
||||
D6EEDE89285FA75F009F854E /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDE88285FA75F009F854E /* Persistence */; };
|
||||
D6EEDE8B285FA7FD009F854E /* LocalData+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */; };
|
||||
D6EEDE92285FA915009F854E /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6EEDE91285FA915009F854E /* WidgetKit.framework */; };
|
||||
D6EEDE94285FA915009F854E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6EEDE93285FA915009F854E /* SwiftUI.framework */; };
|
||||
D6EEDE97285FA915009F854E /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE96285FA915009F854E /* Widgets.swift */; };
|
||||
D6EEDE9A285FA915009F854E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6EEDE99285FA915009F854E /* Assets.xcassets */; };
|
||||
D6EEDE9C285FA915009F854E /* Widgets.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE98285FA915009F854E /* Widgets.intentdefinition */; };
|
||||
D6EEDE9D285FA915009F854E /* Widgets.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE98285FA915009F854E /* Widgets.intentdefinition */; };
|
||||
D6EEDEA0285FA915009F854E /* WidgetsExtension.appex in Embed PlugIns */ = {isa = PBXBuildFile; fileRef = D6EEDE90285FA915009F854E /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D6EEDEA7285FAE4D009F854E /* Recents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDEA5285FAD24009F854E /* Recents.swift */; };
|
||||
D6EEDEA9285FAE60009F854E /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDEA8285FAE60009F854E /* Persistence */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -63,6 +75,13 @@
|
|||
remoteGlobalIDString = D6C687E7272CD27600874C10;
|
||||
remoteInfo = Reader;
|
||||
};
|
||||
D6EEDE9E285FA915009F854E /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6C687E0272CD27600874C10 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D6EEDE8F285FA915009F854E;
|
||||
remoteInfo = WidgetsExtension;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
|
@ -72,6 +91,7 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
D6EEDEA0285FA915009F854E /* WidgetsExtension.appex in Embed PlugIns */,
|
||||
D6840914279487DC00E327D2 /* ReaderMac.bundle in Embed PlugIns */,
|
||||
);
|
||||
name = "Embed PlugIns";
|
||||
|
@ -119,6 +139,8 @@
|
|||
D6C6880B272CD27700874C10 /* ReaderUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReaderUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D6C6880F272CD27700874C10 /* ReaderUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITests.swift; sourceTree = "<group>"; };
|
||||
D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||
D6D5FA23285FE9DB00BBF188 /* WidgetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetHelper.swift; sourceTree = "<group>"; };
|
||||
D6D5FA26285FEA6900BBF188 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = "<group>"; };
|
||||
D6E2434B278B456A0005E546 /* ItemsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsViewController.swift; sourceTree = "<group>"; };
|
||||
D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6E24368278BABB40005E546 /* UIColor+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+App.swift"; sourceTree = "<group>"; };
|
||||
|
@ -128,6 +150,15 @@
|
|||
D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigationController.swift; sourceTree = "<group>"; };
|
||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StretchyMenuInteraction.swift; sourceTree = "<group>"; };
|
||||
D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalData+Migration.swift"; sourceTree = "<group>"; };
|
||||
D6EEDE90285FA915009F854E /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D6EEDE91285FA915009F854E /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
D6EEDE93285FA915009F854E /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
D6EEDE96285FA915009F854E /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = "<group>"; };
|
||||
D6EEDE98285FA915009F854E /* Widgets.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Widgets.intentdefinition; sourceTree = "<group>"; };
|
||||
D6EEDE99285FA915009F854E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
D6EEDE9B285FA915009F854E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D6EEDEA1285FA915009F854E /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = "<group>"; };
|
||||
D6EEDEA5285FAD24009F854E /* Recents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recents.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -162,6 +193,16 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D6EEDE8D285FA915009F854E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6EEDEA9285FAE60009F854E /* Persistence in Frameworks */,
|
||||
D6EEDE94285FA915009F854E /* SwiftUI.framework in Frameworks */,
|
||||
D6EEDE92285FA915009F854E /* WidgetKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
@ -216,6 +257,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D68B3037279099FD00E8B3FA /* liblolhtml.a */,
|
||||
D6EEDE91285FA915009F854E /* WidgetKit.framework */,
|
||||
D6EEDE93285FA915009F854E /* SwiftUI.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -223,12 +266,14 @@
|
|||
D6C687DF272CD27600874C10 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6EEDEA1285FA915009F854E /* WidgetsExtension.entitlements */,
|
||||
D6AB5E9B285F706F00157F2F /* Persistence */,
|
||||
D6AB5E9A285F6FE100157F2F /* Fervor */,
|
||||
D6C687EA272CD27600874C10 /* Reader */,
|
||||
D6C68804272CD27700874C10 /* ReaderTests */,
|
||||
D6C6880E272CD27700874C10 /* ReaderUITests */,
|
||||
D6840911279486C400E327D2 /* ReaderMac */,
|
||||
D6EEDE95285FA915009F854E /* Widgets */,
|
||||
D6C687E9272CD27600874C10 /* Products */,
|
||||
D68B302E278FDCE200E8B3FA /* Frameworks */,
|
||||
);
|
||||
|
@ -241,6 +286,7 @@
|
|||
D6C68801272CD27700874C10 /* ReaderTests.xctest */,
|
||||
D6C6880B272CD27700874C10 /* ReaderUITests.xctest */,
|
||||
D684090D279486BF00E327D2 /* ReaderMac.bundle */,
|
||||
D6EEDE90285FA915009F854E /* WidgetsExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -261,6 +307,7 @@
|
|||
D68408EE2794808E00E327D2 /* Preferences.swift */,
|
||||
D608238C27DE729E00D7D5F9 /* ItemListType.swift */,
|
||||
D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */,
|
||||
D6D5FA25285FEA5B00BBF188 /* Widgets */,
|
||||
D65B18AF2750468B004A9448 /* Screens */,
|
||||
D6C687F7272CD27700874C10 /* Assets.xcassets */,
|
||||
D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */,
|
||||
|
@ -288,6 +335,15 @@
|
|||
path = ReaderUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6D5FA25285FEA5B00BBF188 /* Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6D5FA23285FE9DB00BBF188 /* WidgetHelper.swift */,
|
||||
D6D5FA26285FEA6900BBF188 /* WidgetData.swift */,
|
||||
);
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6E2434A278B455C0005E546 /* Items */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -305,6 +361,18 @@
|
|||
path = Read;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6EEDE95285FA915009F854E /* Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6EEDE96285FA915009F854E /* Widgets.swift */,
|
||||
D6EEDEA5285FAD24009F854E /* Recents.swift */,
|
||||
D6EEDE98285FA915009F854E /* Widgets.intentdefinition */,
|
||||
D6EEDE99285FA915009F854E /* Assets.xcassets */,
|
||||
D6EEDE9B285FA915009F854E /* Info.plist */,
|
||||
);
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -340,6 +408,7 @@
|
|||
);
|
||||
dependencies = (
|
||||
D6840916279487DC00E327D2 /* PBXTargetDependency */,
|
||||
D6EEDE9F285FA915009F854E /* PBXTargetDependency */,
|
||||
);
|
||||
name = Reader;
|
||||
packageProductDependencies = (
|
||||
|
@ -387,6 +456,26 @@
|
|||
productReference = D6C6880B272CD27700874C10 /* ReaderUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
D6EEDE8F285FA915009F854E /* WidgetsExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D6EEDEA2285FA915009F854E /* Build configuration list for PBXNativeTarget "WidgetsExtension" */;
|
||||
buildPhases = (
|
||||
D6EEDE8C285FA915009F854E /* Sources */,
|
||||
D6EEDE8D285FA915009F854E /* Frameworks */,
|
||||
D6EEDE8E285FA915009F854E /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = WidgetsExtension;
|
||||
packageProductDependencies = (
|
||||
D6EEDEA8285FAE60009F854E /* Persistence */,
|
||||
);
|
||||
productName = WidgetsExtension;
|
||||
productReference = D6EEDE90285FA915009F854E /* WidgetsExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
|
@ -394,7 +483,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastSwiftUpdateCheck = 1400;
|
||||
LastUpgradeCheck = 1400;
|
||||
TargetAttributes = {
|
||||
D684090C279486BF00E327D2 = {
|
||||
|
@ -412,6 +501,9 @@
|
|||
CreatedOnToolsVersion = 13.2;
|
||||
TestTargetID = D6C687E7272CD27600874C10;
|
||||
};
|
||||
D6EEDE8F285FA915009F854E = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = D6C687E3272CD27600874C10 /* Build configuration list for PBXProject "Reader" */;
|
||||
|
@ -434,6 +526,7 @@
|
|||
D6C68800272CD27700874C10 /* ReaderTests */,
|
||||
D6C6880A272CD27700874C10 /* ReaderUITests */,
|
||||
D684090C279486BF00E327D2 /* ReaderMac */,
|
||||
D6EEDE8F285FA915009F854E /* WidgetsExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -471,6 +564,14 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D6EEDE8E285FA915009F854E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6EEDE9A285FA915009F854E /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
|
@ -510,6 +611,7 @@
|
|||
files = (
|
||||
D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */,
|
||||
D65B18B627504920004A9448 /* FervorController.swift in Sources */,
|
||||
D6D5FA27285FEA6900BBF188 /* WidgetData.swift in Sources */,
|
||||
D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */,
|
||||
D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */,
|
||||
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */,
|
||||
|
@ -521,9 +623,11 @@
|
|||
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */,
|
||||
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */,
|
||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */,
|
||||
D6EEDE9D285FA915009F854E /* Widgets.intentdefinition in Sources */,
|
||||
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */,
|
||||
D68408EF2794808E00E327D2 /* Preferences.swift in Sources */,
|
||||
D68B303627907D9200E8B3FA /* ExcerptGenerator.swift in Sources */,
|
||||
D6D5FA24285FE9DB00BBF188 /* WidgetHelper.swift in Sources */,
|
||||
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */,
|
||||
D6E2436E278BD8160005E546 /* ReadViewController.swift in Sources */,
|
||||
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */,
|
||||
|
@ -548,6 +652,17 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D6EEDE8C285FA915009F854E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6EEDE9C285FA915009F854E /* Widgets.intentdefinition in Sources */,
|
||||
D6D5FA28285FEA8A00BBF188 /* WidgetData.swift in Sources */,
|
||||
D6EEDEA7285FAE4D009F854E /* Recents.swift in Sources */,
|
||||
D6EEDE97285FA915009F854E /* Widgets.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
|
@ -567,6 +682,11 @@
|
|||
target = D6C687E7272CD27600874C10 /* Reader */;
|
||||
targetProxy = D6C6880C272CD27700874C10 /* PBXContainerItemProxy */;
|
||||
};
|
||||
D6EEDE9F285FA915009F854E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D6EEDE8F285FA915009F854E /* WidgetsExtension */;
|
||||
targetProxy = D6EEDE9E285FA915009F854E /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
|
@ -922,6 +1042,66 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
D6EEDEA3285FA915009F854E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Reader.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D6EEDEA4285FA915009F854E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Reader.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
|
@ -970,6 +1150,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D6EEDEA2285FA915009F854E /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D6EEDEA3285FA915009F854E /* Debug */,
|
||||
D6EEDEA4285FA915009F854E /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
|
@ -997,6 +1186,10 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Persistence;
|
||||
};
|
||||
D6EEDEA8285FAE60009F854E /* Persistence */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Persistence;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = D6C687E0272CD27600874C10 /* Project object */;
|
||||
|
|
|
@ -89,6 +89,8 @@ actor FervorController {
|
|||
|
||||
setSyncState(.excerpts)
|
||||
await ExcerptGenerator.generateAll(self)
|
||||
|
||||
await WidgetHelper.updateWidgetData(fervorController: self)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
<dict>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-unread</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.activate-account</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.add-account</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.preferences</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-all</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-feed</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-group</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-item</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.preferences</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.add-account</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.activate-account</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-unread</string>
|
||||
<string>ConfigurationIntent</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
|
@ -28,10 +29,10 @@
|
|||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PrefsSceneDelegate</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>prefs</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PrefsSceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
|
|
|
@ -95,6 +95,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
Task(priority: .userInitiated) {
|
||||
await fervorController.syncReadToServer()
|
||||
}
|
||||
Task(priority: .userInitiated) {
|
||||
await WidgetHelper.updateWidgetData(fervorController: fervorController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,6 +122,36 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||
let url = URLContexts.first!.url
|
||||
guard url.scheme == "reader",
|
||||
url.host == "item",
|
||||
let itemID = url.pathComponents.last else {
|
||||
logger.error("Launched with unknown URL: \(url.absoluteString, privacy: .public)")
|
||||
return
|
||||
}
|
||||
guard let split = window?.rootViewController as? AppSplitViewController else {
|
||||
logger.error("Could not handle URL: missing split VC")
|
||||
return
|
||||
}
|
||||
let req = Item.fetchRequest()
|
||||
req.predicate = NSPredicate(format: "id = %@", itemID)
|
||||
if let items = try? fervorController.persistentContainer.viewContext.fetch(req),
|
||||
let item = items.first {
|
||||
split.showItem(item)
|
||||
Task {
|
||||
await fervorController.markItem(item, read: true)
|
||||
}
|
||||
} else {
|
||||
Task {
|
||||
if let item = try? await fervorController.fetchItem(id: itemID) {
|
||||
split.showItem(item)
|
||||
await fervorController.markItem(item, read: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupUI(from activity: NSUserActivity) async {
|
||||
guard let split = window?.rootViewController as? AppSplitViewController else {
|
||||
logger.error("Failed to setup UI for user activity: missing split VC")
|
||||
|
|
|
@ -87,6 +87,7 @@ class AppSplitViewController: UISplitViewController {
|
|||
home.selectItem(type)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showItem(_ item: Item) -> ReadViewController {
|
||||
if traitCollection.horizontalSizeClass == .compact {
|
||||
let nav = viewController(for: .compact) as! UINavigationController
|
||||
|
|
|
@ -77,11 +77,14 @@ class ReadViewController: UIViewController {
|
|||
if let content = itemContentHTML() {
|
||||
webView.loadHTMLString(content, baseURL: item.url)
|
||||
}
|
||||
webView.scrollView.alwaysBounceVertical = true
|
||||
webView.scrollView.alwaysBounceHorizontal = false
|
||||
view.addSubview(webView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
// subtract 0.5, because otherwise, on ipad, the web view's scroll content view ends up being wider than the scroll view itself, causing the content to bounce horizontally
|
||||
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -0.5),
|
||||
webView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
|
|
@ -56,7 +56,6 @@ class StretchyMenuInteraction: NSObject, UIInteraction {
|
|||
panRecognizer.delegate = self
|
||||
panRecognizer.allowedScrollTypesMask = [.continuous]
|
||||
view.addGestureRecognizer(panRecognizer)
|
||||
|
||||
}
|
||||
|
||||
private var prevTranslation: CGFloat = 0
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// WidgetData.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 6/19/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Persistence
|
||||
|
||||
struct WidgetData: Codable {
|
||||
let recentItems: [Item]
|
||||
|
||||
private static func url(for account: LocalData.Account) -> URL {
|
||||
let groupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.net.shadowfacts.Reader")!
|
||||
let appSupport = groupContainerURL
|
||||
.appendingPathComponent("Library", isDirectory: true)
|
||||
.appendingPathComponent("Application Support", isDirectory: true)
|
||||
let widgetData = appSupport.appendingPathComponent("Widget Data", isDirectory: true)
|
||||
return widgetData
|
||||
.appendingPathComponent(account.persistenceKey)
|
||||
.appendingPathExtension("plist")
|
||||
}
|
||||
|
||||
static func load(account: LocalData.Account) -> WidgetData {
|
||||
let url = url(for: account)
|
||||
let decoder = PropertyListDecoder()
|
||||
guard let data = try? Data(contentsOf: url),
|
||||
let decoded = try? decoder.decode(WidgetData.self, from: data) else {
|
||||
return WidgetData(recentItems: [])
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
func save(account: LocalData.Account) {
|
||||
let encoder = PropertyListEncoder()
|
||||
let data = try! encoder.encode(self)
|
||||
let url = WidgetData.url(for: account)
|
||||
try! FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try! data.write(to: url, options: .noFileProtection)
|
||||
}
|
||||
|
||||
struct Item: Codable, Identifiable {
|
||||
let id: String
|
||||
let feedTitle: String?
|
||||
let title: String?
|
||||
let published: Date?
|
||||
|
||||
var launchURL: URL {
|
||||
return URL(string: "reader://item/\(id)")!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// WidgetHelper.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 6/19/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import WidgetKit
|
||||
import Persistence
|
||||
|
||||
struct WidgetHelper {
|
||||
private init() {}
|
||||
|
||||
private static let maxDisplayableItems = 8
|
||||
|
||||
static func updateWidgetData(fervorController: FervorController) async {
|
||||
// Accessing CoreData from the widget extension puts us over the memory limit, so we pre-generate all the data it needs and save it to disk
|
||||
|
||||
let prioritizedItems: [WidgetData.Item] = await fervorController.persistentContainer.performBackgroundTask { ctx in
|
||||
let req = Item.fetchRequest()
|
||||
req.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)]
|
||||
req.fetchLimit = 32
|
||||
req.predicate = NSPredicate(format: "read = NO")
|
||||
var items = (try? ctx.fetch(req)) ?? []
|
||||
|
||||
var prioritizedItems: [Item] = []
|
||||
|
||||
while prioritizedItems.count < maxDisplayableItems {
|
||||
let firstFromUnseenFeedIdx = items.firstIndex { item in
|
||||
prioritizedItems.allSatisfy { existing in
|
||||
existing.feed != item.feed
|
||||
}
|
||||
}
|
||||
if let firstFromUnseenFeedIdx {
|
||||
prioritizedItems.append(items.remove(at: firstFromUnseenFeedIdx))
|
||||
} else if let item = items.first {
|
||||
prioritizedItems.append(item)
|
||||
items.removeFirst()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return prioritizedItems.map {
|
||||
WidgetData.Item(id: $0.id!, feedTitle: $0.feed?.title, title: $0.title, published: $0.published)
|
||||
}
|
||||
}
|
||||
|
||||
WidgetData(recentItems: prioritizedItems).save(account: fervorController.account!)
|
||||
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
}
|
||||
}
|
|
@ -16,6 +16,13 @@ body {
|
|||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
@media (min-width: 696px) {
|
||||
body {
|
||||
width: 672px;
|
||||
margin: 12px auto;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--tint-color);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?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>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// Recents.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 6/19/22.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import Persistence
|
||||
|
||||
struct RecentsProvider: IntentTimelineProvider {
|
||||
func placeholder(in context: Context) -> RecentsEntry {
|
||||
RecentsEntry(items: [], configuration: ConfigurationIntent())
|
||||
}
|
||||
|
||||
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (RecentsEntry) -> ()) {
|
||||
let entry = RecentsEntry(items: getItems(), configuration: configuration)
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<RecentsEntry>) -> ()) {
|
||||
// TODO: get account from configuration intent
|
||||
|
||||
let entry = RecentsEntry(items: getItems(), configuration: configuration)
|
||||
let refreshDate = Calendar.current.date(byAdding: .hour, value: 1, to: Date())!
|
||||
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
|
||||
completion(timeline)
|
||||
}
|
||||
|
||||
private func getItems() -> [WidgetData.Item] {
|
||||
guard let account = LocalData.mostRecentAccount() else {
|
||||
return []
|
||||
}
|
||||
return WidgetData.load(account: account).recentItems
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RecentsEntry: TimelineEntry {
|
||||
let date = Date()
|
||||
|
||||
let items: [WidgetData.Item]
|
||||
let configuration: ConfigurationIntent
|
||||
}
|
||||
|
||||
struct RecentsEntryView: View {
|
||||
let entry: RecentsEntry
|
||||
|
||||
@Environment(\.widgetFamily) var family
|
||||
|
||||
var body: some View {
|
||||
if entry.items.isEmpty {
|
||||
} else {
|
||||
switch family {
|
||||
case .systemSmall:
|
||||
SquareItemView(item: entry.items[0])
|
||||
.padding()
|
||||
|
||||
case .systemMedium, .systemLarge:
|
||||
VStack {
|
||||
ForEach(Array(entry.items.prefix(family.maxItemCount).enumerated()), id: \.element.id) { (index, item) in
|
||||
if index != 0 {
|
||||
Divider()
|
||||
}
|
||||
ItemListEntryView(item: item)
|
||||
Spacer(minLength: 4)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
case .systemExtraLarge:
|
||||
if #available(iOS 16.0, *) {
|
||||
VStack {
|
||||
ForEach(Array(stride(from: 0, to: min(entry.items.count, family.maxItemCount), by: 2)), id: \.self) { idx in
|
||||
HStack {
|
||||
ItemListEntryView(item: entry.items[idx])
|
||||
if idx + 1 < entry.items.count {
|
||||
Divider()
|
||||
ItemListEntryView(item: entry.items[idx + 1])
|
||||
}
|
||||
}
|
||||
if idx + 2 < entry.items.count {
|
||||
Spacer()
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
Text("Requires iOS 16")
|
||||
}
|
||||
|
||||
default:
|
||||
fatalError("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension WidgetFamily {
|
||||
var maxItemCount: Int {
|
||||
switch self {
|
||||
case .systemSmall:
|
||||
return 1
|
||||
case .systemMedium:
|
||||
return 2
|
||||
case .systemLarge:
|
||||
return 4
|
||||
case .systemExtraLarge:
|
||||
return 8
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var feedFont = Font.subheadline.weight(.medium).italic()
|
||||
private var titleUIFont: UIFont {
|
||||
// TODO: this should use the compressed SF Pro variant, but there's no API to get at it
|
||||
let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .headline).withSymbolicTraits(.traitCondensed)!
|
||||
return UIFont(descriptor: descriptor, size: 0)
|
||||
}
|
||||
private var titleFont = Font(titleUIFont).leading(.tight)
|
||||
|
||||
struct SquareItemView: View {
|
||||
let item: WidgetData.Item
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text(verbatim: item.feedTitle ?? "")
|
||||
.font(feedFont)
|
||||
.foregroundColor(.red)
|
||||
// force the vstack to be as wide as possible
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
Text(verbatim: item.title ?? "")
|
||||
.font(titleFont)
|
||||
if let published = item.published {
|
||||
Text(published, format: .relative(presentation: .numeric, unitsStyle: .narrow))
|
||||
.font(.caption)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.widgetURL(item.launchURL)
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemListEntryView: View {
|
||||
let item: WidgetData.Item
|
||||
|
||||
var body: some View {
|
||||
Link(destination: item.launchURL) {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text(verbatim: item.feedTitle ?? "")
|
||||
.font(feedFont)
|
||||
.foregroundColor(.red)
|
||||
Spacer()
|
||||
if let published = item.published {
|
||||
Text(published, format: .relative(presentation: .numeric, unitsStyle: .narrow))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
Text(verbatim: item.title ?? "")
|
||||
.font(titleFont)
|
||||
.lineLimit(3)
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Recents_Previews: PreviewProvider {
|
||||
static let item1 = WidgetData.Item(
|
||||
id: "1",
|
||||
feedTitle: "Daring Fireball",
|
||||
title: "There's a Privacy Angle on Apple's Decision to Finance Apple Pay Layer On Its Own",
|
||||
published: Calendar.current.date(byAdding: .hour, value: -1, to: Date())!
|
||||
)
|
||||
static let item2 = WidgetData.Item(
|
||||
id: "2",
|
||||
feedTitle: "Ars Technica",
|
||||
title: "Senate bill would ban data brokers from selling location and health data",
|
||||
published: Calendar.current.date(byAdding: .hour, value: -2, to: Date())!
|
||||
)
|
||||
|
||||
static var previews: some View {
|
||||
// RecentsEntryView(entry: RecentsEntry(items: [item1], configuration: ConfigurationIntent()))
|
||||
// .previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||
|
||||
RecentsEntryView(entry: RecentsEntry(items: [item1, item2], configuration: ConfigurationIntent()))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?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>INEnums</key>
|
||||
<array/>
|
||||
<key>INIntentDefinitionModelVersion</key>
|
||||
<string>1.2</string>
|
||||
<key>INIntentDefinitionNamespace</key>
|
||||
<string>88xZPY</string>
|
||||
<key>INIntentDefinitionSystemVersion</key>
|
||||
<string>22A5266r</string>
|
||||
<key>INIntentDefinitionToolsBuildVersion</key>
|
||||
<string>14A5228q</string>
|
||||
<key>INIntentDefinitionToolsVersion</key>
|
||||
<string>14.0</string>
|
||||
<key>INIntents</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentCategory</key>
|
||||
<string>information</string>
|
||||
<key>INIntentDescriptionID</key>
|
||||
<string>tVvJ9c</string>
|
||||
<key>INIntentEligibleForWidgets</key>
|
||||
<true/>
|
||||
<key>INIntentIneligibleForSuggestions</key>
|
||||
<true/>
|
||||
<key>INIntentLastParameterTag</key>
|
||||
<integer>2</integer>
|
||||
<key>INIntentName</key>
|
||||
<string>Configuration</string>
|
||||
<key>INIntentResponse</key>
|
||||
<dict>
|
||||
<key>INIntentResponseCodes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>success</string>
|
||||
<key>INIntentResponseCodeSuccess</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>failure</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>INIntentTitle</key>
|
||||
<string>Configuration</string>
|
||||
<key>INIntentTitleID</key>
|
||||
<string>gpCwrM</string>
|
||||
<key>INIntentType</key>
|
||||
<string>Custom</string>
|
||||
<key>INIntentVerb</key>
|
||||
<string>View</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INTypes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Widgets.swift
|
||||
// Widgets
|
||||
//
|
||||
// Created by Shadowfacts on 6/19/22.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import Intents
|
||||
|
||||
@main
|
||||
struct Widgets: Widget {
|
||||
var body: some WidgetConfiguration {
|
||||
IntentConfiguration(kind: "recents", intent: ConfigurationIntent.self, provider: RecentsProvider()) { entry in
|
||||
RecentsEntryView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("Recent Articles")
|
||||
.description("Recently posted articles from your feeds.")
|
||||
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||
}
|
||||
}
|
||||
|
|
@ -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.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.net.shadowfacts.Reader</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
Loading…
Reference in New Issue