mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-05 16:58:59 +00:00
parent
e648f1e135
commit
b314b6f289
24 changed files with 1556 additions and 65 deletions
|
@ -12,7 +12,8 @@
|
|||
typedef NS_ENUM(NSUInteger, FeedChooserOperation)
|
||||
{
|
||||
FeedChooserOperationMuteSites = 0,
|
||||
FeedChooserOperationOrganizeSites = 1
|
||||
FeedChooserOperationOrganizeSites = 1,
|
||||
FeedChooserOperationWidgetSites = 2
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
@property (nonatomic) BOOL ascending;
|
||||
@property (nonatomic) BOOL flat;
|
||||
@property (nonatomic, readonly) NewsBlurAppDelegate *appDelegate;
|
||||
@property (nonatomic, strong) NSUserDefaults *groupDefaults;
|
||||
@property (nonatomic, readonly) NSDictionary *widgetFeeds;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -45,10 +47,21 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
|
||||
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
|
||||
|
||||
if (self.operation == FeedChooserOperationWidgetSites) {
|
||||
self.groupDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.newsblur.NewsBlur-Group"];
|
||||
}
|
||||
|
||||
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done)];
|
||||
self.optionsItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"nav_icn_settings.png"] style:UIBarButtonItemStylePlain target:self action:@selector(showOptionsMenu)];
|
||||
|
||||
if (self.operation == FeedChooserOperationOrganizeSites) {
|
||||
if (self.operation == FeedChooserOperationMuteSites) {
|
||||
UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel)];
|
||||
|
||||
self.navigationItem.leftBarButtonItem = cancelItem;
|
||||
self.navigationItem.rightBarButtonItems = @[doneItem, self.optionsItem];
|
||||
|
||||
self.tableView.editing = NO;
|
||||
} else if (self.operation == FeedChooserOperationOrganizeSites) {
|
||||
self.moveItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"menu_icn_move.png"] style:UIBarButtonItemStylePlain target:self action:@selector(showMoveMenu)];
|
||||
self.deleteItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"menu_icn_delete.png"] style:UIBarButtonItemStylePlain target:self action:@selector(deleteFeeds)];
|
||||
|
||||
|
@ -57,12 +70,10 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
|
||||
self.tableView.editing = YES;
|
||||
} else {
|
||||
UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel)];
|
||||
|
||||
self.navigationItem.leftBarButtonItem = cancelItem;
|
||||
self.navigationItem.leftBarButtonItems = nil;
|
||||
self.navigationItem.rightBarButtonItems = @[doneItem, self.optionsItem];
|
||||
|
||||
self.tableView.editing = NO;
|
||||
self.tableView.editing = YES;
|
||||
}
|
||||
|
||||
self.tableView.backgroundColor = UIColorFromRGB(0xECEEEA);
|
||||
|
@ -76,9 +87,13 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
|
||||
if (self.operation == FeedChooserOperationMuteSites) {
|
||||
[self performGetInactiveFeeds];
|
||||
} else if (self.operation == FeedChooserOperationOrganizeSites) {
|
||||
[self updateDictFolders];
|
||||
[self rebuildItemsAnimated:NO];
|
||||
} else {
|
||||
[self updateDictFolders];
|
||||
[self rebuildItemsAnimated:NO];
|
||||
[self updateSelectedWidgets];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,6 +330,10 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
} else {
|
||||
[self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section] animated:YES];
|
||||
}
|
||||
|
||||
if (self.operation == FeedChooserOperationWidgetSites) {
|
||||
[self setWidgetIncludes:select item:item];
|
||||
}
|
||||
}];
|
||||
|
||||
[self updateControls];
|
||||
|
@ -331,8 +350,18 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
} else {
|
||||
self.navigationItem.title = [NSString stringWithFormat:@"Mute %@ Sites", @(count)];
|
||||
}
|
||||
} else {
|
||||
} else if (self.operation == FeedChooserOperationOrganizeSites) {
|
||||
self.navigationItem.title = @"Organize Sites";
|
||||
} else {
|
||||
NSUInteger count = self.tableView.indexPathsForSelectedRows.count;
|
||||
|
||||
if (count == 0) {
|
||||
self.navigationItem.title = @"No Widget Sites";
|
||||
} else if (count == 1) {
|
||||
self.navigationItem.title = @"1 Widget Site";
|
||||
} else {
|
||||
self.navigationItem.title = [NSString stringWithFormat:@"%@ Widget Sites", @(count)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,6 +374,42 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
[self updateTitle];
|
||||
}
|
||||
|
||||
- (NSDictionary *)widgetFeeds {
|
||||
NSMutableDictionary *feeds = [self.groupDefaults objectForKey:@"widget:feeds"];
|
||||
|
||||
if (feeds == nil) {
|
||||
feeds = [NSMutableDictionary dictionary];
|
||||
|
||||
[self enumerateAllRowsUsingBlock:^(NSIndexPath *indexPath, FeedChooserItem *item) {
|
||||
[feeds setObject:item.title forKey:item.identifierString];
|
||||
}];
|
||||
|
||||
[self.groupDefaults setObject:feeds forKey:@"widget:feeds"];
|
||||
}
|
||||
|
||||
return feeds;
|
||||
}
|
||||
|
||||
- (BOOL)widgetIncludesFeed:(NSString *)feedId {
|
||||
return [self.widgetFeeds objectForKey:feedId] != nil;
|
||||
}
|
||||
|
||||
- (void)setWidgetIncludes:(BOOL)include item:(FeedChooserItem *)item {
|
||||
NSMutableDictionary *feeds = [self.widgetFeeds mutableCopy];
|
||||
|
||||
if (include) {
|
||||
[feeds setObject:item.title forKey:item.identifierString];
|
||||
} else {
|
||||
[feeds removeObjectForKey:item.identifierString];
|
||||
}
|
||||
|
||||
[self.groupDefaults setObject:feeds forKey:@"widget:feeds"];
|
||||
}
|
||||
|
||||
- (void)setWidgetIncludes:(BOOL)include itemForIndexPath:(NSIndexPath *)indexPath {
|
||||
[self setWidgetIncludes:include item:[self itemForIndexPath:indexPath]];
|
||||
}
|
||||
|
||||
#pragma mark - Title delegate methods
|
||||
|
||||
- (void)didSelectTitleView:(UIButton *)sender {
|
||||
|
@ -416,8 +481,7 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
[self rebuildItemsAnimated:YES];
|
||||
[self selectItemsWithIdentifiers:identifiers animated:NO];
|
||||
}];
|
||||
|
||||
|
||||
|
||||
MenuItemHandler selectAllHandler = ^{
|
||||
[self enumerateSectionsUsingBlock:^(NSUInteger section, FeedChooserItem *folder) {
|
||||
[self select:YES section:section];
|
||||
|
@ -427,6 +491,7 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
[self select:NO section:section];
|
||||
}];
|
||||
};
|
||||
|
||||
if (isMute) {
|
||||
[viewController addTitle:@"Mute All" iconName:@"mute_feed_off.png" selectionShouldDismiss:YES handler:selectAllHandler];
|
||||
[viewController addTitle:@"Unmute All" iconName:@"mute_feed_on.png" selectionShouldDismiss:YES handler:selectNoneHandler];
|
||||
|
@ -551,6 +616,20 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
self.dictFolders = folders;
|
||||
}
|
||||
|
||||
- (void)updateSelectedWidgets {
|
||||
NSMutableArray *identifiers = [NSMutableArray array];
|
||||
NSDictionary *feeds = self.widgetFeeds;
|
||||
NSArray *feedIds = feeds.allKeys;
|
||||
|
||||
[self enumerateAllRowsUsingBlock:^(NSIndexPath *indexPath, FeedChooserItem *item) {
|
||||
if ([feedIds containsObject:item.identifierString]) {
|
||||
[identifiers addObject:item.identifier];
|
||||
}
|
||||
}];
|
||||
|
||||
[self selectItemsWithIdentifiers:identifiers animated:NO];
|
||||
}
|
||||
|
||||
- (void)performSaveActiveFeeds {
|
||||
[MBProgressHUD hideHUDForView:self.view animated:YES];
|
||||
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
|
||||
|
@ -685,10 +764,18 @@ static const CGFloat kFolderTitleHeight = 36.0;
|
|||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (self.operation == FeedChooserOperationWidgetSites) {
|
||||
[self setWidgetIncludes:YES itemForIndexPath:indexPath];
|
||||
}
|
||||
|
||||
[self updateControls];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (self.operation == FeedChooserOperationWidgetSites) {
|
||||
[self setWidgetIncludes:NO itemForIndexPath:indexPath];
|
||||
}
|
||||
|
||||
[self updateControls];
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
initWithObjects:[@"Preferences" uppercaseString],
|
||||
[@"Mute Sites" uppercaseString],
|
||||
[@"Organize Sites" uppercaseString],
|
||||
[@"Widget Sites" uppercaseString],
|
||||
[@"Notifications" uppercaseString],
|
||||
[@"Find Friends" uppercaseString],
|
||||
[appDelegate.isPremium ? @"Premium Account": @"Upgrade to Premium" uppercaseString],
|
||||
|
@ -56,6 +57,7 @@
|
|||
initWithObjects:[@"Preferences" uppercaseString],
|
||||
[@"Mute Sites" uppercaseString],
|
||||
[@"Organize Sites" uppercaseString],
|
||||
[@"Widget Sites" uppercaseString],
|
||||
[@"Notifications" uppercaseString],
|
||||
[@"Find Friends" uppercaseString],
|
||||
[appDelegate.isPremium ? @"Premium Account": @"Upgrade to Premium" uppercaseString],
|
||||
|
@ -171,22 +173,26 @@
|
|||
break;
|
||||
|
||||
case 3:
|
||||
image = [UIImage imageNamed:@"menu_icn_widget.png"];
|
||||
break;
|
||||
|
||||
case 4:
|
||||
image = [UIImage imageNamed:@"menu_icn_notifications.png"];
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 5:
|
||||
image = [UIImage imageNamed:@"menu_icn_followers.png"];
|
||||
break;
|
||||
|
||||
case 5:
|
||||
case 6:
|
||||
image = [UIImage imageNamed:@"g_icn_greensun.png"];
|
||||
break;
|
||||
|
||||
case 6:
|
||||
case 7:
|
||||
image = [UIImage imageNamed:@"menu_icn_fetch_subscribers.png"];
|
||||
break;
|
||||
|
||||
case 7:
|
||||
case 8:
|
||||
image = [UIImage imageNamed:@"barbutton_sendto.png"];
|
||||
break;
|
||||
|
||||
|
@ -225,22 +231,26 @@
|
|||
break;
|
||||
|
||||
case 3:
|
||||
[appDelegate showWidgetSites];
|
||||
break;
|
||||
|
||||
case 4:
|
||||
[appDelegate openNotificationsWithFeed:nil];
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 5:
|
||||
[appDelegate showFindFriends];
|
||||
break;
|
||||
|
||||
case 5:
|
||||
case 6:
|
||||
[appDelegate showPremiumDialog];
|
||||
break;
|
||||
|
||||
case 6:
|
||||
case 7:
|
||||
[appDelegate confirmLogout];
|
||||
break;
|
||||
|
||||
case 7:
|
||||
case 8:
|
||||
[self showLoginAsDialog];
|
||||
break;
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -22,7 +20,7 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="12">
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="12">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
@ -31,7 +29,7 @@
|
|||
<outlet property="delegate" destination="-1" id="15"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" id="WaK-km-cVb">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" translatesAutoresizingMaskIntoConstraints="NO" id="WaK-km-cVb">
|
||||
<rect key="frame" x="43" y="216" width="234" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -44,7 +42,7 @@
|
|||
<action selector="changeTheme:" destination="-1" eventType="valueChanged" id="lQ3-gY-fUx"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" id="tAD-6D-jDd">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" translatesAutoresizingMaskIntoConstraints="NO" id="tAD-6D-jDd">
|
||||
<rect key="frame" x="56" y="252" width="209" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -62,6 +60,7 @@
|
|||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="139" y="154"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" colorMatched="YES">
|
||||
<device id="ipad9_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
|
||||
<device id="ipad9_7" orientation="portrait" layout="fullscreen" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -26,7 +24,7 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="240" height="222"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" id="28">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" translatesAutoresizingMaskIntoConstraints="NO" id="28">
|
||||
<rect key="frame" x="3" y="81" width="234" height="30"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -40,7 +38,7 @@
|
|||
<action selector="changeFontSize:" destination="-1" eventType="valueChanged" id="29"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" id="4iD-hO-qU2">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" translatesAutoresizingMaskIntoConstraints="NO" id="4iD-hO-qU2">
|
||||
<rect key="frame" x="3" y="123" width="234" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -54,7 +52,7 @@
|
|||
<action selector="changeLineSpacing:" destination="-1" eventType="valueChanged" id="3hE-hD-2Aq"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" id="muZ-4L-Qll">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" translatesAutoresizingMaskIntoConstraints="NO" id="muZ-4L-Qll">
|
||||
<rect key="frame" x="3" y="96" width="234" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -65,7 +63,7 @@
|
|||
<action selector="changeScrollOrientation:" destination="-1" eventType="valueChanged" id="x5R-7P-7ha"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" id="RRB-to-azP">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" translatesAutoresizingMaskIntoConstraints="NO" id="RRB-to-azP">
|
||||
<rect key="frame" x="3" y="96" width="234" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -76,7 +74,7 @@
|
|||
<action selector="changeFullscreen:" destination="-1" eventType="valueChanged" id="h3Y-T6-P9J"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" id="DbL-5f-bAN">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" translatesAutoresizingMaskIntoConstraints="NO" id="DbL-5f-bAN">
|
||||
<rect key="frame" x="3" y="96" width="234" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -87,7 +85,7 @@
|
|||
<action selector="changeAutoscroll:" destination="-1" eventType="valueChanged" id="dFh-eV-zfx"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" id="9Zd-0Z-SQt">
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="2" translatesAutoresizingMaskIntoConstraints="NO" id="9Zd-0Z-SQt">
|
||||
<rect key="frame" x="3" y="97" width="234" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
|
@ -100,7 +98,7 @@
|
|||
<action selector="changeTheme:" destination="-1" eventType="valueChanged" id="d7d-9h-DPN"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="35">
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="35">
|
||||
<rect key="frame" x="0.0" y="-1" width="240" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
@ -113,14 +111,15 @@
|
|||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="139" y="154"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="line_spacing_l.png" width="32" height="32"/>
|
||||
<image name="line_spacing_m.png" width="32" height="32"/>
|
||||
<image name="line_spacing_s.png" width="32" height="32"/>
|
||||
<image name="line_spacing_xl.png" width="32" height="32"/>
|
||||
<image name="line_spacing_xs.png" width="32" height="32"/>
|
||||
<image name="line_spacing_l.png" width="32.450702667236328" height="32.450702667236328"/>
|
||||
<image name="line_spacing_m.png" width="32.450702667236328" height="32.450702667236328"/>
|
||||
<image name="line_spacing_s.png" width="32.450702667236328" height="32.450702667236328"/>
|
||||
<image name="line_spacing_xl.png" width="32.450702667236328" height="32.450702667236328"/>
|
||||
<image name="line_spacing_xs.png" width="32.450702667236328" height="32.450702667236328"/>
|
||||
<image name="theme_color_dark.png" width="32" height="32"/>
|
||||
<image name="theme_color_light.png" width="32" height="32"/>
|
||||
<image name="theme_color_medium.png" width="32" height="32"/>
|
||||
|
|
|
@ -307,11 +307,13 @@ SFSafariViewControllerDelegate> {
|
|||
- (void)showFindFriends;
|
||||
- (void)showMuteSites;
|
||||
- (void)showOrganizeSites;
|
||||
- (void)showWidgetSites;
|
||||
- (void)showPremiumDialog;
|
||||
- (void)showPreferences;
|
||||
- (void)setHiddenPreferencesAnimated:(BOOL)animated;
|
||||
- (void)resizePreviewSize;
|
||||
- (void)resizeFontSize;
|
||||
- (void)popToRoot;
|
||||
|
||||
- (void)showMoveSite;
|
||||
- (void)openTrainSite;
|
||||
|
|
|
@ -515,15 +515,7 @@
|
|||
}];
|
||||
} else if ([action isEqualToString:@"VIEW_STORY_IDENTIFIER"] ||
|
||||
[action isEqualToString:@"com.apple.UNNotificationDefaultActionIdentifier"]) {
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
[masterContainerViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
[self.navigationController
|
||||
popToViewController:[self.navigationController.viewControllers
|
||||
objectAtIndex:0]
|
||||
animated:YES];
|
||||
} else {
|
||||
[self.navigationController popToRootViewControllerAnimated:NO];
|
||||
}
|
||||
[self popToRoot];
|
||||
[self loadFeed:feedIdStr withStory:storyHash animated:NO];
|
||||
if (completionHandler) completionHandler();
|
||||
} else if ([action isEqualToString:@"DISMISS_IDENTIFIER"]) {
|
||||
|
@ -555,6 +547,36 @@
|
|||
openURL:(NSURL *)url
|
||||
sourceApplication:(NSString *)sourceApplication
|
||||
annotation:(id)annotation {
|
||||
if (self.activeUsername && [url.scheme isEqualToString:@"newsblurwidget"]) {
|
||||
NSMutableDictionary *query = [NSMutableDictionary dictionary];
|
||||
|
||||
for (NSString *component in [url.query componentsSeparatedByString:@"&"]) {
|
||||
NSArray *keyAndValue = [component componentsSeparatedByString:@"="];
|
||||
|
||||
[query setObject:keyAndValue.lastObject forKey:keyAndValue.firstObject];
|
||||
}
|
||||
|
||||
NSString *feedId = query[@"feedId"];
|
||||
NSString *storyHash = query[@"storyHash"];
|
||||
NSString *error = query[@"error"];
|
||||
|
||||
if (error.length) {
|
||||
[self popToRoot];
|
||||
[self showWidgetSites];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!feedId.length || !storyHash.length) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self popToRoot];
|
||||
[self loadFeed:feedId withStory:storyHash animated:NO];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
@ -720,6 +742,17 @@
|
|||
[feedsViewController resizeFontSize];
|
||||
}
|
||||
|
||||
- (void)popToRoot {
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
[masterContainerViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
[self.navigationController
|
||||
popToViewController:[self.navigationController.viewControllers objectAtIndex:0]
|
||||
animated:YES];
|
||||
} else {
|
||||
[self.navigationController popToRootViewControllerAnimated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showPremiumDialog {
|
||||
UINavigationController *navController = self.navigationController;
|
||||
if (self.premiumNavigationController == nil) {
|
||||
|
@ -825,6 +858,10 @@
|
|||
[self showFeedChooserForOperation:FeedChooserOperationOrganizeSites];
|
||||
}
|
||||
|
||||
- (void)showWidgetSites {
|
||||
[self showFeedChooserForOperation:FeedChooserOperationWidgetSites];
|
||||
}
|
||||
|
||||
- (void)showFindFriends {
|
||||
[self hidePopover];
|
||||
|
||||
|
|
|
@ -566,9 +566,10 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
|
||||
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.newsblur.NewsBlur-Group"];
|
||||
[defaults setObject:[results objectForKey:@"share_ext_token"] forKey:@"share:token"];
|
||||
[defaults setObject:DEFAULT_NEWSBLUR_URL forKey:@"share:host"];
|
||||
[defaults setObject:self.appDelegate.url forKey:@"share:host"];
|
||||
[self validateWidgetFeedsForGroupDefaults:defaults usingResults:results];
|
||||
[defaults synchronize];
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
|
||||
(unsigned long)NULL), ^(void) {
|
||||
[appDelegate.database inTransaction:^(FMDatabase *db, BOOL *rollback) {
|
||||
|
@ -1160,6 +1161,25 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
|
|||
}
|
||||
}
|
||||
|
||||
- (void)validateWidgetFeedsForGroupDefaults:(NSUserDefaults *)groupDefaults usingResults:(NSDictionary *)results {
|
||||
NSMutableDictionary *feeds = [groupDefaults objectForKey:@"widget:feeds"];
|
||||
|
||||
if (feeds == nil) {
|
||||
feeds = [NSMutableDictionary dictionary];
|
||||
|
||||
NSDictionary *resultsFeeds = results[@"feeds"];
|
||||
|
||||
[resultsFeeds enumerateKeysAndObjectsUsingBlock:^(id key, NSDictionary *obj, BOOL *stop) {
|
||||
NSString *identifier = [NSString stringWithFormat:@"%@", key];
|
||||
NSString *title = obj[@"feed_title"];
|
||||
|
||||
feeds[identifier] = title;
|
||||
}];
|
||||
|
||||
[groupDefaults setObject:feeds forKey:@"widget:feeds"];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Table View - Feed List
|
||||
|
||||
|
|
|
@ -56,6 +56,16 @@
|
|||
<string>pocketapp16638</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.newsblur.NewsBlur.widget</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>newsblurwidget</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
010EDEFA1B2386B7003B79DE /* OnePasswordExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 010EDEF81B2386B7003B79DE /* OnePasswordExtension.m */; };
|
||||
010EDEFC1B238722003B79DE /* 1Password.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 010EDEFB1B238722003B79DE /* 1Password.xcassets */; };
|
||||
17042DB92391D68A001BCD32 /* WidgetStory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17042DB82391D68A001BCD32 /* WidgetStory.swift */; };
|
||||
17042DBB23922A4D001BCD32 /* WidgetLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17042DBA23922A4D001BCD32 /* WidgetLoader.swift */; };
|
||||
1715D02B2166B3F900227731 /* PremiumManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1715D02A2166B3F900227731 /* PremiumManager.m */; };
|
||||
17362ADD23639B4E00A0FCCC /* OfflineFetchText.m in Sources */ = {isa = PBXBuildFile; fileRef = 17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */; };
|
||||
1740C6881C10FD75005EA453 /* theme_color_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6841C10FD75005EA453 /* theme_color_dark.png */; };
|
||||
|
@ -42,10 +44,16 @@
|
|||
175065911C5730FB00072BF5 /* barbutton_selection_off@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1750658E1C5730FB00072BF5 /* barbutton_selection_off@3x.png */; };
|
||||
175696A61C596ABC004C128D /* menu_icn_all.png in Resources */ = {isa = PBXBuildFile; fileRef = 175696A41C596ABC004C128D /* menu_icn_all.png */; };
|
||||
175696A71C596ABC004C128D /* menu_icn_all@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 175696A51C596ABC004C128D /* menu_icn_all@2x.png */; };
|
||||
175FAC4C23AB34EB002AC38C /* menu_icn_widget.png in Resources */ = {isa = PBXBuildFile; fileRef = 175FAC4A23AB34EB002AC38C /* menu_icn_widget.png */; };
|
||||
175FAC4D23AB34EB002AC38C /* menu_icn_widget@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 175FAC4B23AB34EB002AC38C /* menu_icn_widget@2x.png */; };
|
||||
176129601C630AEB00702FE4 /* mute_feed_off.png in Resources */ = {isa = PBXBuildFile; fileRef = 1761295C1C630AEB00702FE4 /* mute_feed_off.png */; };
|
||||
176129611C630AEB00702FE4 /* mute_feed_off@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1761295D1C630AEB00702FE4 /* mute_feed_off@2x.png */; };
|
||||
176129621C630AEB00702FE4 /* mute_feed_on.png in Resources */ = {isa = PBXBuildFile; fileRef = 1761295E1C630AEB00702FE4 /* mute_feed_on.png */; };
|
||||
176129631C630AEB00702FE4 /* mute_feed_on@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1761295F1C630AEB00702FE4 /* mute_feed_on@2x.png */; };
|
||||
177551D5238E228A00E27818 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 177551D4238E228A00E27818 /* NotificationCenter.framework */; };
|
||||
177551DB238E228A00E27818 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 177551D9238E228A00E27818 /* MainInterface.storyboard */; };
|
||||
177551DF238E228A00E27818 /* NewsBlur Latest.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 177551D3238E228A00E27818 /* NewsBlur Latest.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
17813FB723AC6E450057FB16 /* WidgetErrorTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17CE3F0523AC529B003152EF /* WidgetErrorTableViewCell.xib */; };
|
||||
17876B9E1C9911D40055DD15 /* g_icn_folder_rss_sm.png in Resources */ = {isa = PBXBuildFile; fileRef = 17876B9A1C9911D40055DD15 /* g_icn_folder_rss_sm.png */; };
|
||||
17876B9F1C9911D40055DD15 /* g_icn_folder_rss_sm@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17876B9B1C9911D40055DD15 /* g_icn_folder_rss_sm@2x.png */; };
|
||||
17876BA01C9911D40055DD15 /* g_icn_folder_sm.png in Resources */ = {isa = PBXBuildFile; fileRef = 17876B9C1C9911D40055DD15 /* g_icn_folder_sm.png */; };
|
||||
|
@ -94,6 +102,7 @@
|
|||
17CBD3BF1BF66B6C003FCCAE /* MarkReadMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17CBD3BE1BF66B6C003FCCAE /* MarkReadMenuViewController.m */; };
|
||||
17CBD3C21BF6ED2C003FCCAE /* menu_icn_markread.png in Resources */ = {isa = PBXBuildFile; fileRef = 17CBD3C01BF6ED2C003FCCAE /* menu_icn_markread.png */; };
|
||||
17CBD3C31BF6ED2C003FCCAE /* menu_icn_markread@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17CBD3C11BF6ED2C003FCCAE /* menu_icn_markread@2x.png */; };
|
||||
17CE3F0723AC529E003152EF /* WidgetErrorTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17CE3F0623AC529E003152EF /* WidgetErrorTableViewCell.swift */; };
|
||||
17CF7DD41C5C6AE40067BC5B /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17CF7DD31C5C6AE40067BC5B /* WebKit.framework */; };
|
||||
17E265DE1C0D17340060655F /* storyDetailViewDark.css in Resources */ = {isa = PBXBuildFile; fileRef = 17E265DD1C0D17340060655F /* storyDetailViewDark.css */; };
|
||||
17E57D571C0E592600EB3D4B /* storyDetailViewMedium.css in Resources */ = {isa = PBXBuildFile; fileRef = 17E57D551C0E592600EB3D4B /* storyDetailViewMedium.css */; };
|
||||
|
@ -105,11 +114,15 @@
|
|||
17E635A81C5432220075338E /* barbutton_selection@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17E635A21C5432220075338E /* barbutton_selection@2x.png */; };
|
||||
17E635A91C5432220075338E /* barbutton_selection@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17E635A31C5432220075338E /* barbutton_selection@3x.png */; };
|
||||
17E635AF1C548C580075338E /* FeedChooserItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E635AE1C548C580075338E /* FeedChooserItem.m */; };
|
||||
17E86ED5238E444B00863EC8 /* WidgetTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17E86ED3238E444B00863EC8 /* WidgetTableViewCell.xib */; };
|
||||
17E86ED6238E444B00863EC8 /* WidgetTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E86ED4238E444B00863EC8 /* WidgetTableViewCell.swift */; };
|
||||
17EB505C1BE4411E0021358B /* choose_font.png in Resources */ = {isa = PBXBuildFile; fileRef = 17EB505A1BE4411E0021358B /* choose_font.png */; };
|
||||
17EB505D1BE4411E0021358B /* choose_font@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17EB505B1BE4411E0021358B /* choose_font@2x.png */; };
|
||||
17EB50601BE46A900021358B /* FontListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17EB505F1BE46A900021358B /* FontListViewController.m */; };
|
||||
17EB50621BE46BB00021358B /* FontListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17EB50611BE46BB00021358B /* FontListViewController.xib */; };
|
||||
17F156711BDABBF60092EBFD /* safari_shadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17F156701BDABBF60092EBFD /* safari_shadow@2x.png */; };
|
||||
17F363F2238E417300D5379D /* WidgetExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17F363EF238E417300D5379D /* WidgetExtensionViewController.swift */; };
|
||||
17FB51D723AC81C500F5D5BF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 17FB51D923AC81C500F5D5BF /* InfoPlist.strings */; };
|
||||
1D3623260D0F684500981E51 /* NewsBlurAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* NewsBlurAppDelegate.m */; };
|
||||
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
|
||||
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
|
||||
|
@ -604,6 +617,13 @@
|
|||
remoteGlobalIDString = 1749390F1C251BFE003D98AA;
|
||||
remoteInfo = "Share Extension";
|
||||
};
|
||||
177551DD238E228A00E27818 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 177551D2238E228A00E27818;
|
||||
remoteInfo = widget;
|
||||
};
|
||||
FF8A949D1DE3BB77000A4C31 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */;
|
||||
|
@ -631,6 +651,7 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
177551DF238E228A00E27818 /* NewsBlur Latest.appex in Embed App Extensions */,
|
||||
1749391B1C251BFE003D98AA /* Share Extension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
|
@ -642,6 +663,8 @@
|
|||
010EDEF81B2386B7003B79DE /* OnePasswordExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OnePasswordExtension.m; path = "Other Sources/OnePasswordExtension/OnePasswordExtension.m"; sourceTree = "<group>"; };
|
||||
010EDEF91B2386B7003B79DE /* OnePasswordExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OnePasswordExtension.h; path = "Other Sources/OnePasswordExtension/OnePasswordExtension.h"; sourceTree = "<group>"; };
|
||||
010EDEFB1B238722003B79DE /* 1Password.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = 1Password.xcassets; path = "Other Sources/OnePasswordExtension/1Password.xcassets"; sourceTree = "<group>"; };
|
||||
17042DB82391D68A001BCD32 /* WidgetStory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetStory.swift; sourceTree = "<group>"; };
|
||||
17042DBA23922A4D001BCD32 /* WidgetLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetLoader.swift; sourceTree = "<group>"; };
|
||||
1715D0292166B3F900227731 /* PremiumManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PremiumManager.h; sourceTree = "<group>"; };
|
||||
1715D02A2166B3F900227731 /* PremiumManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PremiumManager.m; sourceTree = "<group>"; };
|
||||
17362ADB23639B4E00A0FCCC /* OfflineFetchText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OfflineFetchText.h; path = offline/OfflineFetchText.h; sourceTree = "<group>"; };
|
||||
|
@ -684,10 +707,17 @@
|
|||
1750658E1C5730FB00072BF5 /* barbutton_selection_off@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "barbutton_selection_off@3x.png"; sourceTree = "<group>"; };
|
||||
175696A41C596ABC004C128D /* menu_icn_all.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_icn_all.png; sourceTree = "<group>"; };
|
||||
175696A51C596ABC004C128D /* menu_icn_all@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icn_all@2x.png"; sourceTree = "<group>"; };
|
||||
175FAC4A23AB34EB002AC38C /* menu_icn_widget.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_icn_widget.png; sourceTree = "<group>"; };
|
||||
175FAC4B23AB34EB002AC38C /* menu_icn_widget@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icn_widget@2x.png"; sourceTree = "<group>"; };
|
||||
1761295C1C630AEB00702FE4 /* mute_feed_off.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mute_feed_off.png; sourceTree = "<group>"; };
|
||||
1761295D1C630AEB00702FE4 /* mute_feed_off@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "mute_feed_off@2x.png"; sourceTree = "<group>"; };
|
||||
1761295E1C630AEB00702FE4 /* mute_feed_on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = mute_feed_on.png; sourceTree = "<group>"; };
|
||||
1761295F1C630AEB00702FE4 /* mute_feed_on@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "mute_feed_on@2x.png"; sourceTree = "<group>"; };
|
||||
177551D3238E228A00E27818 /* NewsBlur Latest.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NewsBlur Latest.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
177551D4238E228A00E27818 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; };
|
||||
177551DA238E228A00E27818 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
177551DC238E228A00E27818 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
177551E3238E26BF00E27818 /* Widget Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Widget Extension.entitlements"; sourceTree = "<group>"; };
|
||||
17876B9A1C9911D40055DD15 /* g_icn_folder_rss_sm.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = g_icn_folder_rss_sm.png; sourceTree = "<group>"; };
|
||||
17876B9B1C9911D40055DD15 /* g_icn_folder_rss_sm@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "g_icn_folder_rss_sm@2x.png"; sourceTree = "<group>"; };
|
||||
17876B9C1C9911D40055DD15 /* g_icn_folder_sm.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = g_icn_folder_sm.png; sourceTree = "<group>"; };
|
||||
|
@ -739,6 +769,8 @@
|
|||
17CBD3BE1BF66B6C003FCCAE /* MarkReadMenuViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MarkReadMenuViewController.m; sourceTree = "<group>"; };
|
||||
17CBD3C01BF6ED2C003FCCAE /* menu_icn_markread.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_icn_markread.png; sourceTree = "<group>"; };
|
||||
17CBD3C11BF6ED2C003FCCAE /* menu_icn_markread@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icn_markread@2x.png"; sourceTree = "<group>"; };
|
||||
17CE3F0523AC529B003152EF /* WidgetErrorTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WidgetErrorTableViewCell.xib; sourceTree = "<group>"; };
|
||||
17CE3F0623AC529E003152EF /* WidgetErrorTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetErrorTableViewCell.swift; sourceTree = "<group>"; };
|
||||
17CF7DD31C5C6AE40067BC5B /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
|
||||
17E265DD1C0D17340060655F /* storyDetailViewDark.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = storyDetailViewDark.css; path = static/storyDetailViewDark.css; sourceTree = SOURCE_ROOT; };
|
||||
17E57D551C0E592600EB3D4B /* storyDetailViewMedium.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = storyDetailViewMedium.css; path = static/storyDetailViewMedium.css; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -751,12 +783,16 @@
|
|||
17E635A31C5432220075338E /* barbutton_selection@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "barbutton_selection@3x.png"; sourceTree = "<group>"; };
|
||||
17E635AD1C548C580075338E /* FeedChooserItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedChooserItem.h; sourceTree = "<group>"; };
|
||||
17E635AE1C548C580075338E /* FeedChooserItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedChooserItem.m; sourceTree = "<group>"; };
|
||||
17E86ED3238E444B00863EC8 /* WidgetTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WidgetTableViewCell.xib; sourceTree = "<group>"; };
|
||||
17E86ED4238E444B00863EC8 /* WidgetTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetTableViewCell.swift; sourceTree = "<group>"; };
|
||||
17EB505A1BE4411E0021358B /* choose_font.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = choose_font.png; sourceTree = "<group>"; };
|
||||
17EB505B1BE4411E0021358B /* choose_font@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "choose_font@2x.png"; sourceTree = "<group>"; };
|
||||
17EB505E1BE46A900021358B /* FontListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FontListViewController.h; sourceTree = "<group>"; };
|
||||
17EB505F1BE46A900021358B /* FontListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FontListViewController.m; sourceTree = "<group>"; };
|
||||
17EB50611BE46BB00021358B /* FontListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = FontListViewController.xib; path = Classes/FontListViewController.xib; sourceTree = SOURCE_ROOT; };
|
||||
17F156701BDABBF60092EBFD /* safari_shadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "safari_shadow@2x.png"; sourceTree = "<group>"; };
|
||||
17F363EF238E417300D5379D /* WidgetExtensionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetExtensionViewController.swift; sourceTree = "<group>"; };
|
||||
17FB51D823AC81C500F5D5BF /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
1D3623240D0F684500981E51 /* NewsBlurAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = NewsBlurAppDelegate.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
1D3623250D0F684500981E51 /* NewsBlurAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = NewsBlurAppDelegate.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
|
@ -1436,6 +1472,14 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
177551D0238E228A00E27818 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
177551D5238E228A00E27818 /* NotificationCenter.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1D60588F0D05DD3D006BFB54 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -1570,12 +1614,31 @@
|
|||
path = fonts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
177551D6238E228A00E27818 /* Widget Extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
17F363EF238E417300D5379D /* WidgetExtensionViewController.swift */,
|
||||
17E86ED4238E444B00863EC8 /* WidgetTableViewCell.swift */,
|
||||
17E86ED3238E444B00863EC8 /* WidgetTableViewCell.xib */,
|
||||
17CE3F0623AC529E003152EF /* WidgetErrorTableViewCell.swift */,
|
||||
17CE3F0523AC529B003152EF /* WidgetErrorTableViewCell.xib */,
|
||||
17042DB82391D68A001BCD32 /* WidgetStory.swift */,
|
||||
17042DBA23922A4D001BCD32 /* WidgetLoader.swift */,
|
||||
177551D9238E228A00E27818 /* MainInterface.storyboard */,
|
||||
177551E3238E26BF00E27818 /* Widget Extension.entitlements */,
|
||||
177551DC238E228A00E27818 /* Info.plist */,
|
||||
17FB51D923AC81C500F5D5BF /* InfoPlist.strings */,
|
||||
);
|
||||
path = "Widget Extension";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
19C28FACFE9D520D11CA2CBB /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1D6058910D05DD3D006BFB54 /* NewsBlur.app */,
|
||||
174939101C251BFE003D98AA /* Share Extension.appex */,
|
||||
FF8A94971DE3BB77000A4C31 /* Story Notification Service Extension.appex */,
|
||||
177551D3238E228A00E27818 /* NewsBlur Latest.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1590,6 +1653,7 @@
|
|||
29B97315FDCFA39411CA2CEA /* Other Sources */,
|
||||
174939111C251BFE003D98AA /* Share Extension */,
|
||||
FF8A94981DE3BB77000A4C31 /* Story Notification Service Extension */,
|
||||
177551D6238E228A00E27818 /* Widget Extension */,
|
||||
29B97323FDCFA39411CA2CEA /* Frameworks */,
|
||||
19C28FACFE9D520D11CA2CBB /* Products */,
|
||||
FF8C49921BBC9D140010D894 /* NewsBlur.entitlements */,
|
||||
|
@ -1686,6 +1750,7 @@
|
|||
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */,
|
||||
FF8D1EBC1BAA311000725D8A /* SBJSON */,
|
||||
FF8D1EA41BAA304E00725D8A /* Reachability */,
|
||||
177551D4238E228A00E27818 /* NotificationCenter.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1924,6 +1989,8 @@
|
|||
17E635981C5431F50075338E /* menu_icn_mute@2x.png */,
|
||||
17E635991C5431F50075338E /* menu_icn_organize.png */,
|
||||
FF877CD21C6541F2007940C3 /* menu_icn_organize@2x.png */,
|
||||
175FAC4A23AB34EB002AC38C /* menu_icn_widget.png */,
|
||||
175FAC4B23AB34EB002AC38C /* menu_icn_widget@2x.png */,
|
||||
FF688E5016E6B8D0003B7B42 /* traverse_background.png */,
|
||||
FF688E5116E6B8D0003B7B42 /* traverse_background@2x.png */,
|
||||
FF688E4C16E6B3E1003B7B42 /* traverse_done.png */,
|
||||
|
@ -2685,6 +2752,23 @@
|
|||
productReference = 174939101C251BFE003D98AA /* Share Extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
177551D2238E228A00E27818 /* Widget Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 177551E2238E228A00E27818 /* Build configuration list for PBXNativeTarget "Widget Extension" */;
|
||||
buildPhases = (
|
||||
177551CF238E228A00E27818 /* Sources */,
|
||||
177551D0238E228A00E27818 /* Frameworks */,
|
||||
177551D1238E228A00E27818 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Widget Extension";
|
||||
productName = widget;
|
||||
productReference = 177551D3238E228A00E27818 /* NewsBlur Latest.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
1D6058900D05DD3D006BFB54 /* NewsBlur */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "NewsBlur" */;
|
||||
|
@ -2700,6 +2784,7 @@
|
|||
dependencies = (
|
||||
1749391A1C251BFE003D98AA /* PBXTargetDependency */,
|
||||
FF8A949E1DE3BB77000A4C31 /* PBXTargetDependency */,
|
||||
177551DE238E228A00E27818 /* PBXTargetDependency */,
|
||||
);
|
||||
name = NewsBlur;
|
||||
productName = NewsBlur;
|
||||
|
@ -2729,6 +2814,7 @@
|
|||
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1120;
|
||||
LastUpgradeCheck = 1110;
|
||||
ORGANIZATIONNAME = NewsBlur;
|
||||
TargetAttributes = {
|
||||
|
@ -2741,6 +2827,12 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
177551D2238E228A00E27818 = {
|
||||
CreatedOnToolsVersion = 11.2.1;
|
||||
DevelopmentTeam = HR7P97SD72;
|
||||
LastSwiftMigration = 1120;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
1D6058900D05DD3D006BFB54 = {
|
||||
DevelopmentTeam = HR7P97SD72;
|
||||
LastSwiftMigration = 1020;
|
||||
|
@ -2804,6 +2896,7 @@
|
|||
1D6058900D05DD3D006BFB54 /* NewsBlur */,
|
||||
1749390F1C251BFE003D98AA /* Share Extension */,
|
||||
FF8A94961DE3BB77000A4C31 /* Story Notification Service Extension */,
|
||||
177551D2238E228A00E27818 /* Widget Extension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -2817,6 +2910,17 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
177551D1238E228A00E27818 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
17813FB723AC6E450057FB16 /* WidgetErrorTableViewCell.xib in Resources */,
|
||||
17FB51D723AC81C500F5D5BF /* InfoPlist.strings in Resources */,
|
||||
177551DB238E228A00E27818 /* MainInterface.storyboard in Resources */,
|
||||
17E86ED5238E444B00863EC8 /* WidgetTableViewCell.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1D60588D0D05DD3D006BFB54 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -2905,6 +3009,7 @@
|
|||
43A4C49B15B00A26008787B5 /* warning.png in Resources */,
|
||||
17E265DE1C0D17340060655F /* storyDetailViewDark.css in Resources */,
|
||||
17C67D9F2138B2D20027CCAE /* traverse_previous_vert@2x.png in Resources */,
|
||||
175FAC4D23AB34EB002AC38C /* menu_icn_widget@2x.png in Resources */,
|
||||
43A4C49C15B00A26008787B5 /* world.png in Resources */,
|
||||
43C1680B15B3D99B00428BA3 /* 7-location-place.png in Resources */,
|
||||
43B6A27515B6952F00CEA2E6 /* group.png in Resources */,
|
||||
|
@ -2923,6 +3028,7 @@
|
|||
43A4BADC15C866FA00F3B8D4 /* popoverArrowDown@2x.png in Resources */,
|
||||
17AACFEA22279A3C00DE6EA4 /* autoscroll_resume.png in Resources */,
|
||||
43A4BADD15C866FA00F3B8D4 /* popoverArrowDownSimple.png in Resources */,
|
||||
175FAC4C23AB34EB002AC38C /* menu_icn_widget.png in Resources */,
|
||||
43A4BADE15C866FA00F3B8D4 /* popoverArrowLeft.png in Resources */,
|
||||
FFC486AC19CA410000F4758F /* logo_50.png in Resources */,
|
||||
176129601C630AEB00702FE4 /* mute_feed_off.png in Resources */,
|
||||
|
@ -3246,6 +3352,18 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
177551CF238E228A00E27818 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
17CE3F0723AC529E003152EF /* WidgetErrorTableViewCell.swift in Sources */,
|
||||
17E86ED6238E444B00863EC8 /* WidgetTableViewCell.swift in Sources */,
|
||||
17F363F2238E417300D5379D /* WidgetExtensionViewController.swift in Sources */,
|
||||
17042DB92391D68A001BCD32 /* WidgetStory.swift in Sources */,
|
||||
17042DBB23922A4D001BCD32 /* WidgetLoader.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1D60588E0D05DD3D006BFB54 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -3420,6 +3538,11 @@
|
|||
target = 1749390F1C251BFE003D98AA /* Share Extension */;
|
||||
targetProxy = 174939191C251BFE003D98AA /* PBXContainerItemProxy */;
|
||||
};
|
||||
177551DE238E228A00E27818 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 177551D2238E228A00E27818 /* Widget Extension */;
|
||||
targetProxy = 177551DD238E228A00E27818 /* PBXContainerItemProxy */;
|
||||
};
|
||||
FF8A949E1DE3BB77000A4C31 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = FF8A94961DE3BB77000A4C31 /* Story Notification Service Extension */;
|
||||
|
@ -3436,6 +3559,22 @@
|
|||
name = MainInterface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
177551D9238E228A00E27818 /* MainInterface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
177551DA238E228A00E27818 /* Base */,
|
||||
);
|
||||
name = MainInterface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
17FB51D923AC81C500F5D5BF /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
17FB51D823AC81C500F5D5BF /* en */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FF34FD411E9D93CB0062F8ED /* IASKLocalizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -3481,7 +3620,6 @@
|
|||
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
@ -3501,7 +3639,6 @@
|
|||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.newsblur.NewsBlur.Share-Extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -3531,7 +3668,6 @@
|
|||
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
@ -3545,7 +3681,6 @@
|
|||
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.newsblur.NewsBlur.Share-Extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -3556,9 +3691,97 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
177551E0238E228A00E27818 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
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_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "Widget Extension/Widget Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = "Widget Extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.newsblur.NewsBlur.widget;
|
||||
PRODUCT_NAME = "NewsBlur Latest";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
177551E1238E228A00E27818 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
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_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "Widget Extension/Widget Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = "Widget Extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.newsblur.NewsBlur.widget;
|
||||
PRODUCT_NAME = "NewsBlur Latest";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
1D6058940D05DD3E006BFB54 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
|
@ -3566,7 +3789,6 @@
|
|||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -3587,7 +3809,6 @@
|
|||
"\"$(SRCROOT)\"",
|
||||
"\"$(SRCROOT)/Other Sources\"",
|
||||
);
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
|
||||
OTHER_LDFLAGS = (
|
||||
"-lsqlite3.0",
|
||||
|
@ -3609,6 +3830,7 @@
|
|||
1D6058950D05DD3E006BFB54 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
|
@ -3616,7 +3838,6 @@
|
|||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -3636,7 +3857,6 @@
|
|||
"\"$(SRCROOT)\"",
|
||||
"\"$(SRCROOT)/Other Sources\"",
|
||||
);
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"-lsqlite3.0",
|
||||
"-ObjC",
|
||||
|
@ -3675,6 +3895,7 @@
|
|||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
|
@ -3688,6 +3909,7 @@
|
|||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/**";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PROVISIONING_PROFILE = "";
|
||||
|
@ -3720,6 +3942,7 @@
|
|||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = "compiler-default";
|
||||
|
@ -3732,6 +3955,7 @@
|
|||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/**";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
|
@ -3757,7 +3981,6 @@
|
|||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
|
@ -3772,7 +3995,6 @@
|
|||
INFOPLIST_FILE = "Story Notification Service Extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.newsblur.NewsBlur.Story-Notification-Service-Extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -3795,7 +4017,6 @@
|
|||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEVELOPMENT_TEAM = HR7P97SD72;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
|
@ -3804,7 +4025,6 @@
|
|||
INFOPLIST_FILE = "Story Notification Service Extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 9.1.1;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.newsblur.NewsBlur.Story-Notification-Service-Extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -3826,6 +4046,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
177551E2238E228A00E27818 /* Build configuration list for PBXNativeTarget "Widget Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
177551E0238E228A00E27818 /* Debug */,
|
||||
177551E1238E228A00E27818 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "NewsBlur" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1120"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "177551D2238E228A00E27818"
|
||||
BuildableName = "NewsBlur Stories.appex"
|
||||
BlueprintName = "Widget Extension"
|
||||
ReferencedContainer = "container:NewsBlur.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
|
||||
BuildableName = "NewsBlur.app"
|
||||
BlueprintName = "NewsBlur"
|
||||
ReferencedContainer = "container:NewsBlur.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "1"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "177551D2238E228A00E27818"
|
||||
BuildableName = "NewsBlur Stories.appex"
|
||||
BlueprintName = "Widget Extension"
|
||||
ReferencedContainer = "container:NewsBlur.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
|
||||
BuildableName = "NewsBlur.app"
|
||||
BlueprintName = "NewsBlur"
|
||||
ReferencedContainer = "container:NewsBlur.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
|
||||
BuildableName = "NewsBlur.app"
|
||||
BlueprintName = "NewsBlur"
|
||||
ReferencedContainer = "container:NewsBlur.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
BIN
clients/ios/Resources/menu_icn_widget.png
Normal file
BIN
clients/ios/Resources/menu_icn_widget.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
BIN
clients/ios/Resources/menu_icn_widget@2x.png
Normal file
BIN
clients/ios/Resources/menu_icn_widget@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y4P-Rc-9qS">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Widget Extension View Controller-->
|
||||
<scene sceneID="rRX-uD-WSg">
|
||||
<objects>
|
||||
<tableViewController id="Y4P-Rc-9qS" customClass="WidgetExtensionViewController" customModule="NewsBlur_" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="WP5-UW-zbV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="200"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Y4P-Rc-9qS" id="JAi-3i-yO4"/>
|
||||
<outlet property="delegate" destination="Y4P-Rc-9qS" id="DMT-QM-cAI"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<nil key="simulatedTopBarMetrics"/>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="375" height="200"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="598-tA-QtX" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="30" y="57"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
31
clients/ios/Widget Extension/Info.plist
Normal file
31
clients/ios/Widget Extension/Info.plist
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?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>$(PRODUCT_NAME)</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>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widget-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
10
clients/ios/Widget Extension/Widget Extension.entitlements
Normal file
10
clients/ios/Widget Extension/Widget Extension.entitlements
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?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.com.newsblur.NewsBlur-Group</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
16
clients/ios/Widget Extension/WidgetErrorTableViewCell.swift
Normal file
16
clients/ios/Widget Extension/WidgetErrorTableViewCell.swift
Normal file
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// WidgetErrorTableViewCell.swift
|
||||
// Widget Extension
|
||||
//
|
||||
// Created by David Sinclair on 2019-11-26.
|
||||
// Copyright © 2019 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class WidgetErrorTableViewCell: UITableViewCell {
|
||||
/// The reuse identifier for this table view cell.
|
||||
static let reuseIdentifier = "WidgetErrorTableViewCell"
|
||||
|
||||
@IBOutlet var errorLabel: UILabel!
|
||||
}
|
38
clients/ios/Widget Extension/WidgetErrorTableViewCell.xib
Normal file
38
clients/ios/Widget Extension/WidgetErrorTableViewCell.xib
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="110" id="KGk-i7-Jjx" customClass="WidgetErrorTableViewCell" customModule="NewsBlur_" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjx" id="H2p-sc-9ux">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="<error>" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c8v-22-SQx">
|
||||
<rect key="frame" x="130.5" y="44.5" width="59" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="c8v-22-SQx" firstAttribute="centerX" secondItem="H2p-sc-9ux" secondAttribute="centerX" id="rUZ-br-Z2x"/>
|
||||
<constraint firstItem="c8v-22-SQx" firstAttribute="centerY" secondItem="H2p-sc-9ux" secondAttribute="centerY" id="uTo-nZ-Tdx"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oax"/>
|
||||
<connections>
|
||||
<outlet property="errorLabel" destination="c8v-22-SQx" id="2zs-Gq-Owx"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="33.600000000000001" y="85.457271364317847"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
549
clients/ios/Widget Extension/WidgetExtensionViewController.swift
Normal file
549
clients/ios/Widget Extension/WidgetExtensionViewController.swift
Normal file
|
@ -0,0 +1,549 @@
|
|||
//
|
||||
// WidgetViewController.swift
|
||||
// Widget Extension
|
||||
//
|
||||
// Created by David Sinclair on 2019-11-26.
|
||||
// Copyright © 2019 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import NotificationCenter
|
||||
|
||||
enum WidgetError: String, Error {
|
||||
case notLoggedIn
|
||||
case loading
|
||||
case noFeeds
|
||||
case noStories
|
||||
}
|
||||
|
||||
class WidgetExtensionViewController: UITableViewController, NCWidgetProviding {
|
||||
/// The base URL of the NewsBlur server.
|
||||
var host: String?
|
||||
|
||||
/// The secret token for authentication.
|
||||
var token: String?
|
||||
|
||||
/// Dictionary of feed IDs and names.
|
||||
typealias FeedsDictionary = [String : String]
|
||||
|
||||
/// A dictionary of feed IDs and names to fetch.
|
||||
var feeds = FeedsDictionary()
|
||||
|
||||
/// Loaded stories.
|
||||
var stories = [Story]()
|
||||
|
||||
/// An error to display instead of the stories, or `nil` if the stories should be displayed.
|
||||
var error: WidgetError?
|
||||
|
||||
struct Constant {
|
||||
static let group = "group.com.newsblur.NewsBlur-Group"
|
||||
static let token = "share:token"
|
||||
static let host = "share:host"
|
||||
static let feeds = "widget:feeds"
|
||||
static let widgetFolder = "Widget"
|
||||
static let storiesFilename = "Stories.json"
|
||||
static let imageExtension = "png"
|
||||
static let limit = 5
|
||||
static let defaultRowHeight: CGFloat = 110
|
||||
static let storyImageSize: CGFloat = 64 * 3
|
||||
}
|
||||
|
||||
// MARK: - View lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Allow the today widget to be expanded or contracted.
|
||||
extensionContext?.widgetLargestAvailableDisplayMode = .expanded
|
||||
|
||||
loadCachedStories()
|
||||
|
||||
// Register the table view cell.
|
||||
let widgetTableViewCellNib = UINib(nibName: "WidgetTableViewCell", bundle: nil)
|
||||
tableView.register(widgetTableViewCellNib, forCellReuseIdentifier: WidgetTableViewCell.reuseIdentifier)
|
||||
|
||||
let errorTableViewCellNib = UINib(nibName: "WidgetErrorTableViewCell", bundle: nil)
|
||||
tableView.register(errorTableViewCellNib, forCellReuseIdentifier: WidgetErrorTableViewCell.reuseIdentifier)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
extensionContext?.widgetLargestAvailableDisplayMode = error == nil ? .expanded : .compact
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
|
||||
imageCache.removeAll()
|
||||
}
|
||||
|
||||
// MARK: - Widget provider protocol
|
||||
|
||||
typealias AnyDictionary = [String : Any]
|
||||
|
||||
typealias WidgetCompletion = (NCUpdateResult) -> Void
|
||||
|
||||
private var widgetCompletion: WidgetCompletion?
|
||||
|
||||
private typealias ImageDictionary = [String : UIImage]
|
||||
private var imageCache = ImageDictionary()
|
||||
|
||||
private typealias LoaderDictionary = [String : Loader]
|
||||
private var loaders = LoaderDictionary()
|
||||
|
||||
func widgetPerformUpdate(completionHandler: (@escaping WidgetCompletion)) {
|
||||
if feeds.isEmpty {
|
||||
if error == .noFeeds {
|
||||
completionHandler(.noData)
|
||||
} else {
|
||||
error = .noFeeds
|
||||
completionHandler(.newData)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let combinedFeeds = feeds.keys.joined(separator: "&f=")
|
||||
|
||||
guard let url = hostURL(with: "/reader/river_stories/?include_hidden=false&page=1&infrequent=false&order=newest&read_filter=unread&limit=\(Constant.limit)&f=\(combinedFeeds)") else {
|
||||
completionHandler(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
error = nil
|
||||
widgetCompletion = completionHandler
|
||||
loaders[Constant.storiesFilename] = Loader(url: url, completion: storyLoaderCompletion(result:))
|
||||
}
|
||||
|
||||
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
|
||||
switch activeDisplayMode {
|
||||
case .compact:
|
||||
// The compact view is a fixed size.
|
||||
preferredContentSize = maxSize
|
||||
case .expanded:
|
||||
let height: CGFloat = rowHeight * CGFloat(numberOfTableRowsToDisplay)
|
||||
|
||||
preferredContentSize = CGSize(width: maxSize.width, height: min(height, maxSize.height))
|
||||
@unknown default:
|
||||
preconditionFailure("Unexpected value for activeDisplayMode.")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Content container protocol
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
let updatedVisibleCellCount = numberOfTableRowsToDisplay
|
||||
let currentVisibleCellCount = self.tableView.visibleCells.count
|
||||
let cellCountDifference = updatedVisibleCellCount - currentVisibleCellCount
|
||||
|
||||
// If the number of visible cells has changed, animate them in/out along with the resize animation.
|
||||
if cellCountDifference != 0 {
|
||||
coordinator.animate(alongsideTransition: { [unowned self] (UIViewControllerTransitionCoordinatorContext) in
|
||||
self.tableView.performBatchUpdates({ [unowned self] in
|
||||
// Build an array of IndexPath objects representing the rows to be inserted or deleted.
|
||||
let range = (1...abs(cellCountDifference))
|
||||
let indexPaths = range.map({ (index) -> IndexPath in
|
||||
return IndexPath(row: index, section: 0)
|
||||
})
|
||||
|
||||
// Animate the insertion or deletion of the rows.
|
||||
if cellCountDifference > 0 {
|
||||
self.tableView.insertRows(at: indexPaths, with: .fade)
|
||||
} else {
|
||||
self.tableView.deleteRows(at: indexPaths, with: .fade)
|
||||
}
|
||||
}, completion: nil)
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return numberOfTableRowsToDisplay
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
precondition(Thread.isMainThread, "Table access not on the main thread")
|
||||
|
||||
if let error = error {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: WidgetErrorTableViewCell.reuseIdentifier, for: indexPath) as? WidgetErrorTableViewCell else {
|
||||
preconditionFailure("Expected to dequeue a WidgetErrorTableViewCell")
|
||||
}
|
||||
|
||||
switch error {
|
||||
case .notLoggedIn:
|
||||
cell.errorLabel.text = "Please log in to NewsBlur"
|
||||
case .loading:
|
||||
cell.errorLabel.text = "On its way..."
|
||||
case .noFeeds:
|
||||
cell.errorLabel.text = "Please choose sites to show"
|
||||
case .noStories:
|
||||
cell.errorLabel.text = "No stories for selected sites"
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: WidgetTableViewCell.reuseIdentifier, for: indexPath) as? WidgetTableViewCell
|
||||
else {
|
||||
preconditionFailure("Expected to dequeue a WidgetTableViewCell")
|
||||
}
|
||||
|
||||
let story = stories[indexPath.row]
|
||||
|
||||
cell.feedImageView.image = nil
|
||||
|
||||
// Completion handler passes the feed to confirm that this cell still wants that image (i.e. hasn't been reused).
|
||||
feedImage(for: story.feed) { (image, feed) in
|
||||
if story.feed == feed {
|
||||
cell.feedImageView.image = image
|
||||
}
|
||||
}
|
||||
|
||||
if let name = feeds[story.feed] {
|
||||
cell.feedLabel.text = name
|
||||
} else {
|
||||
cell.feedLabel.text = ""
|
||||
}
|
||||
|
||||
cell.titleLabel.text = cleaned(story.title)
|
||||
cell.contentLabel.text = cleaned(story.content)
|
||||
cell.authorLabel.text = cleaned(story.author).uppercased()
|
||||
cell.dateLabel.text = story.date
|
||||
cell.thumbnailImageView.image = nil
|
||||
|
||||
storyImage(for: story.id, imageURL: story.imageURL) { (image, id) in
|
||||
if story.id == id {
|
||||
cell.thumbnailImageView.image = image
|
||||
}
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
return rowHeight
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if let error = error {
|
||||
if let appURL = URL(string: "newsblurwidget://?error=\(error.rawValue)") {
|
||||
extensionContext?.open(appURL, completionHandler: nil)
|
||||
}
|
||||
} else {
|
||||
let story = stories[indexPath.row]
|
||||
if let appURL = URL(string: "newsblurwidget://?feedId=\(story.feed)&storyHash=\(story.id)") {
|
||||
extensionContext?.open(appURL, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private extension WidgetExtensionViewController {
|
||||
var numberOfTableRowsToDisplay: Int {
|
||||
if stories.isEmpty, error == nil {
|
||||
error = .loading
|
||||
}
|
||||
|
||||
if error != nil {
|
||||
return 1
|
||||
} else if extensionContext?.widgetActiveDisplayMode == NCWidgetDisplayMode.compact {
|
||||
return 1
|
||||
} else {
|
||||
return min(stories.count, Constant.limit)
|
||||
}
|
||||
}
|
||||
|
||||
var rowHeight: CGFloat {
|
||||
return extensionContext?.widgetMaximumSize(for: .compact).height ?? Constant.defaultRowHeight
|
||||
}
|
||||
|
||||
func cleaned(_ string: String) -> String {
|
||||
let clean = string.prefix(1000).replacingOccurrences(of: "\n", with: "")
|
||||
.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil)
|
||||
.replacingOccurrences(of: "&[^;]+;", with: " ", options: .regularExpression, range: nil)
|
||||
.trimmingCharacters(in: .whitespaces)
|
||||
|
||||
return clean.isEmpty ? " " : clean
|
||||
}
|
||||
|
||||
func hostURL(with path: String) -> URL? {
|
||||
guard let host = host else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let token = token {
|
||||
return URL(string: host + path + "&secret_token=\(token)")
|
||||
} else {
|
||||
return URL(string: host + path)
|
||||
}
|
||||
}
|
||||
|
||||
func storyLoaderCompletion(result: Result<Data, Error>) {
|
||||
defer {
|
||||
widgetCompletion = nil
|
||||
loaders[Constant.storiesFilename] = nil
|
||||
}
|
||||
|
||||
if case .failure = result {
|
||||
widgetCompletion?(.failed)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard case .success(let data) = result else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? AnyDictionary else {
|
||||
widgetCompletion?(.failed)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard let storyArray = dictionary["stories"] as? [AnyDictionary] else {
|
||||
widgetCompletion?(.failed)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stories.removeAll()
|
||||
|
||||
for storyDict in storyArray {
|
||||
stories.append(Story(from: storyDict))
|
||||
}
|
||||
|
||||
saveStories()
|
||||
|
||||
if stories.isEmpty, error == nil {
|
||||
error = .noStories
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.extensionContext?.widgetLargestAvailableDisplayMode = self.error == nil ? .expanded : .compact
|
||||
|
||||
self.tableView.reloadData()
|
||||
self.tableView.setNeedsDisplay()
|
||||
|
||||
self.widgetCompletion?(.newData)
|
||||
}
|
||||
}
|
||||
|
||||
var groupContainerURL: URL? {
|
||||
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constant.group)
|
||||
}
|
||||
|
||||
var widgetFolderURL: URL? {
|
||||
return groupContainerURL?.appendingPathComponent(Constant.widgetFolder)
|
||||
}
|
||||
|
||||
var storiesURL: URL? {
|
||||
return widgetFolderURL?.appendingPathComponent(Constant.storiesFilename)
|
||||
}
|
||||
|
||||
func createWidgetFolder() {
|
||||
guard let url = widgetFolderURL else {
|
||||
return
|
||||
}
|
||||
|
||||
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
func loadCachedStories() {
|
||||
stories = []
|
||||
|
||||
guard let defaults = UserDefaults.init(suiteName: Constant.group) else {
|
||||
return
|
||||
}
|
||||
|
||||
host = defaults.string(forKey: Constant.host)
|
||||
token = defaults.string(forKey: Constant.token)
|
||||
|
||||
if let dict = defaults.dictionary(forKey: Constant.feeds) as? FeedsDictionary {
|
||||
feeds = dict
|
||||
}
|
||||
|
||||
guard let url = storiesURL else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let json = try Data(contentsOf: url)
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
decoder.dateDecodingStrategy = .iso8601
|
||||
|
||||
stories = try decoder.decode([Story].self, from: json)
|
||||
} catch {
|
||||
print("Error \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func saveStories() {
|
||||
guard let url = storiesURL else {
|
||||
return
|
||||
}
|
||||
|
||||
createWidgetFolder()
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
|
||||
encoder.dateEncodingStrategy = .iso8601
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
|
||||
do {
|
||||
let json = try encoder.encode(stories)
|
||||
|
||||
try json.write(to: url)
|
||||
} catch {
|
||||
print("Error \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func cachedImage(for identifier: String) -> UIImage? {
|
||||
if let image = imageCache[identifier] {
|
||||
return image
|
||||
}
|
||||
|
||||
guard let folderURL = widgetFolderURL else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let imageURL = folderURL.appendingPathComponent(identifier).appendingPathExtension(Constant.imageExtension)
|
||||
let data = try Data(contentsOf: imageURL)
|
||||
|
||||
guard let image = UIImage(data: data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
imageCache[identifier] = image
|
||||
|
||||
return image
|
||||
} catch {
|
||||
print("Image error: \(error)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func save(image: UIImage, for identifier: String) {
|
||||
guard let folderURL = widgetFolderURL else {
|
||||
return
|
||||
}
|
||||
|
||||
imageCache[identifier] = image
|
||||
|
||||
createWidgetFolder()
|
||||
|
||||
do {
|
||||
let imageURL = folderURL.appendingPathComponent(identifier).appendingPathExtension(Constant.imageExtension)
|
||||
|
||||
try image.pngData()?.write(to: imageURL)
|
||||
} catch {
|
||||
print("Image error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
typealias ImageCompletion = (UIImage?, String?) -> Void
|
||||
|
||||
func feedImage(for feed: String, completion: @escaping ImageCompletion) {
|
||||
guard let url = hostURL(with: "/reader/favicons?feed_ids=\(feed)") else {
|
||||
completion(nil, feed)
|
||||
return
|
||||
}
|
||||
|
||||
if let image = cachedImage(for: feed) {
|
||||
completion(image, feed)
|
||||
return
|
||||
}
|
||||
|
||||
loaders[feed] = Loader(url: url) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
defer {
|
||||
self.loaders[feed] = nil
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let data):
|
||||
guard let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? AnyDictionary,
|
||||
let base64 = dictionary[feed] as? String,
|
||||
let imageData = Data(base64Encoded: base64, options: .ignoreUnknownCharacters),
|
||||
let image = UIImage(data: imageData) else {
|
||||
completion(nil, feed)
|
||||
return
|
||||
}
|
||||
|
||||
self.save(image: image, for: feed)
|
||||
completion(image, feed)
|
||||
case .failure:
|
||||
completion(nil, feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func storyImage(for identifier: String, imageURL: URL?, completion: @escaping ImageCompletion) {
|
||||
guard let url = imageURL else {
|
||||
completion(nil, identifier)
|
||||
return
|
||||
}
|
||||
|
||||
if let image = cachedImage(for: identifier) {
|
||||
completion(image, identifier)
|
||||
return
|
||||
}
|
||||
|
||||
loaders[identifier] = Loader(url: url) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
defer {
|
||||
self.loaders[identifier] = nil
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let data):
|
||||
guard let loadedImage = UIImage(data: data) else {
|
||||
completion(nil, identifier)
|
||||
return
|
||||
}
|
||||
|
||||
let scaledImage = self.scale(image: loadedImage)
|
||||
|
||||
self.save(image: scaledImage, for: identifier)
|
||||
completion(scaledImage, identifier)
|
||||
case .failure:
|
||||
completion(nil, identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scale(image: UIImage) -> UIImage {
|
||||
guard image.size.width > Constant.storyImageSize || image.size.height > Constant.storyImageSize else {
|
||||
return image
|
||||
}
|
||||
|
||||
let size = CGSize(width: Constant.storyImageSize, height: Constant.storyImageSize)
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(size, true, 1)
|
||||
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
|
||||
defer {
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
return UIGraphicsGetImageFromCurrentImageContext() ?? image
|
||||
}
|
||||
}
|
67
clients/ios/Widget Extension/WidgetLoader.swift
Normal file
67
clients/ios/Widget Extension/WidgetLoader.swift
Normal file
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// WidgetLoader.swift
|
||||
// Widget Extension
|
||||
//
|
||||
// Created by David Sinclair on 2019-11-29.
|
||||
// Copyright © 2019 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Network loader for the widget.
|
||||
class Loader: NSObject, URLSessionDataDelegate {
|
||||
typealias Completion = (Result<Data, Error>) -> Void
|
||||
|
||||
private var completion: Completion
|
||||
|
||||
private var receivedData = Data()
|
||||
|
||||
init(url: URL, completion: @escaping Completion) {
|
||||
self.completion = completion
|
||||
|
||||
super.init()
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
|
||||
request.httpMethod = "GET"
|
||||
// request.addValue(accept, forHTTPHeaderField: "Accept")
|
||||
|
||||
let config = URLSessionConfiguration.background(withIdentifier: UUID().uuidString)
|
||||
config.sharedContainerIdentifier = "group.com.newsblur.NewsBlur-Group"
|
||||
|
||||
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
|
||||
let task = session.dataTask(with: request)
|
||||
|
||||
task.resume()
|
||||
}
|
||||
|
||||
// MARK: - URL session delegate
|
||||
|
||||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||||
guard let response = response as? HTTPURLResponse,
|
||||
(200...299).contains(response.statusCode) else {
|
||||
completionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
|
||||
completionHandler(.allow)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
|
||||
print("error: \(error.debugDescription)")
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
||||
print("data: \(data)")
|
||||
|
||||
receivedData.append(data)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
} else {
|
||||
completion(.success(receivedData))
|
||||
}
|
||||
}
|
||||
}
|
122
clients/ios/Widget Extension/WidgetStory.swift
Normal file
122
clients/ios/Widget Extension/WidgetStory.swift
Normal file
|
@ -0,0 +1,122 @@
|
|||
//
|
||||
// WidgetStory.swift
|
||||
// Widget Extension
|
||||
//
|
||||
// Created by David Sinclair on 2019-11-29.
|
||||
// Copyright © 2019 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// A story to display in the widget.
|
||||
struct Story: Codable, Identifiable {
|
||||
/// The version number.
|
||||
let version = 1
|
||||
|
||||
/// The story hash.
|
||||
let id: String
|
||||
|
||||
/// The feed ID.
|
||||
let feed: String
|
||||
|
||||
/// The date and/or time as a string.
|
||||
let date: String
|
||||
|
||||
/// The author of the story.
|
||||
let author: String
|
||||
|
||||
/// The title of the story.
|
||||
let title: String
|
||||
|
||||
/// The content of the story.
|
||||
let content: String
|
||||
|
||||
/// The URL of the image, or `nil` if none.
|
||||
let imageURL: URL?
|
||||
|
||||
/// Keys for the dictionary representation.
|
||||
struct DictionaryKeys {
|
||||
static let id = "story_hash"
|
||||
static let feed = "story_feed_id"
|
||||
static let date = "short_parsed_date"
|
||||
static let author = "story_authors"
|
||||
static let title = "story_title"
|
||||
static let content = "story_content"
|
||||
static let imageURLs = "image_urls"
|
||||
}
|
||||
|
||||
/// Initializer from a dictionary.
|
||||
///
|
||||
/// - Parameter dictionary: Dictionary from the server.
|
||||
init(from dictionary: [String : Any]) {
|
||||
id = dictionary[DictionaryKeys.id] as? String ?? ""
|
||||
feed = dictionary[DictionaryKeys.feed] as? String ?? "\(dictionary[DictionaryKeys.feed] as? Int ?? 0)"
|
||||
date = dictionary[DictionaryKeys.date] as? String ?? ""
|
||||
author = dictionary[DictionaryKeys.author] as? String ?? ""
|
||||
title = dictionary[DictionaryKeys.title] as? String ?? ""
|
||||
content = dictionary[DictionaryKeys.content] as? String ?? ""
|
||||
|
||||
if let images = dictionary[DictionaryKeys.imageURLs] as? [String], let first = images.first {
|
||||
imageURL = URL(string: first)
|
||||
} else {
|
||||
imageURL = nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Keys for the codable representation.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case version = "version"
|
||||
case id = "id"
|
||||
case feed = "feed"
|
||||
case date = "date"
|
||||
case author = "author"
|
||||
case title = "title"
|
||||
case content = "content"
|
||||
case imageURL = "imageURL"
|
||||
}
|
||||
|
||||
/// Initializer to load from the JSON data.
|
||||
///
|
||||
/// - Parameter decoder: The decoder from which to read data.
|
||||
/// - Throws: An error if the data is invalid.
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
id = try container.decode(String.self, forKey: .id)
|
||||
feed = try container.decode(String.self, forKey: .feed)
|
||||
date = try container.decode(String.self, forKey: .date)
|
||||
author = try container.decode(String.self, forKey: .author)
|
||||
title = try container.decode(String.self, forKey: .title)
|
||||
content = try container.decode(String.self, forKey: .content)
|
||||
imageURL = try container.decodeIfPresent(URL.self, forKey: .imageURL)
|
||||
}
|
||||
|
||||
/// Encodes the story into the given encoder.
|
||||
///
|
||||
/// - Parameter encoder: The encoder to which to write data.
|
||||
/// - Throws: An error if the data is invalid.
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(version, forKey: .version)
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(feed, forKey: .feed)
|
||||
try container.encode(date, forKey: .date)
|
||||
try container.encode(author, forKey: .author)
|
||||
try container.encode(title, forKey: .title)
|
||||
try container.encode(content, forKey: .content)
|
||||
try container.encodeIfPresent(imageURL, forKey: .imageURL)
|
||||
}
|
||||
}
|
||||
|
||||
extension Story: Equatable {
|
||||
static func ==(lhs: Story, rhs: Story) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
extension Story: CustomStringConvertible {
|
||||
var description: String {
|
||||
return "Story \(title) by \(author) (\(id))"
|
||||
}
|
||||
}
|
22
clients/ios/Widget Extension/WidgetTableViewCell.swift
Normal file
22
clients/ios/Widget Extension/WidgetTableViewCell.swift
Normal file
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// WidgetTableViewCell.swift
|
||||
// Widget Extension
|
||||
//
|
||||
// Created by David Sinclair on 2019-11-26.
|
||||
// Copyright © 2019 NewsBlur. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class WidgetTableViewCell: UITableViewCell {
|
||||
/// The reuse identifier for this table view cell.
|
||||
static let reuseIdentifier = "WidgetTableViewCell"
|
||||
|
||||
@IBOutlet var feedImageView: UIImageView!
|
||||
@IBOutlet var feedLabel: UILabel!
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
@IBOutlet var contentLabel: UILabel!
|
||||
@IBOutlet var authorLabel: UILabel!
|
||||
@IBOutlet var dateLabel: UILabel!
|
||||
@IBOutlet var thumbnailImageView: UIImageView!
|
||||
}
|
103
clients/ios/Widget Extension/WidgetTableViewCell.xib
Normal file
103
clients/ios/Widget Extension/WidgetTableViewCell.xib
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="110" id="KGk-i7-Jjw" customClass="WidgetTableViewCell" customModule="NewsBlur_Latest" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="110"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="<date>" textAlignment="natural" lineBreakMode="headTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="02U-BY-b7a">
|
||||
<rect key="frame" x="265.5" y="91" width="34.5" height="12"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="10"/>
|
||||
<color key="textColor" systemColor="tertiaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" horizontalCompressionResistancePriority="745" verticalCompressionResistancePriority="745" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="232" translatesAutoresizingMaskIntoConstraints="NO" id="I7A-fd-pwi">
|
||||
<rect key="frame" x="20" y="27" width="208" height="33.5"/>
|
||||
<string key="text"><title>
|
||||
(2 lines)</string>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QWb-zc-x4f">
|
||||
<rect key="frame" x="236" y="23" width="64" height="64"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="64" id="GrS-OY-t9T"/>
|
||||
<constraint firstAttribute="height" constant="64" id="ID2-Sx-mif"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="<author>" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Wai-eu-jJp">
|
||||
<rect key="frame" x="20" y="91" width="44.5" height="12"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="10"/>
|
||||
<color key="textColor" systemColor="tertiaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="<feed>" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pv8-bs-3wb">
|
||||
<rect key="frame" x="44" y="7" width="256" height="12"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="10"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="el3-VK-r6t">
|
||||
<rect key="frame" x="20" y="5" width="16" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="16" id="dCu-9e-TSf"/>
|
||||
<constraint firstAttribute="width" constant="16" id="qzQ-Ql-xKX"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="232" translatesAutoresizingMaskIntoConstraints="NO" id="YTq-d7-IZe">
|
||||
<rect key="frame" x="20" y="60.5" width="208" height="30.5"/>
|
||||
<string key="text"><content>
|
||||
(2 lines)</string>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="12"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="QWb-zc-x4f" secondAttribute="trailing" constant="20" symbolic="YES" id="0AJ-OQ-qEF"/>
|
||||
<constraint firstItem="I7A-fd-pwi" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="20" symbolic="YES" id="1Oj-8N-Kbm"/>
|
||||
<constraint firstAttribute="trailing" relation="lessThanOrEqual" secondItem="pv8-bs-3wb" secondAttribute="trailing" constant="20" symbolic="YES" id="510-Ep-Tcu"/>
|
||||
<constraint firstItem="YTq-d7-IZe" firstAttribute="top" secondItem="I7A-fd-pwi" secondAttribute="bottom" id="74T-Ba-a26"/>
|
||||
<constraint firstItem="el3-VK-r6t" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="5" id="BHE-Ve-mwT"/>
|
||||
<constraint firstItem="QWb-zc-x4f" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="Cbg-Kp-Sw2"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Wai-eu-jJp" secondAttribute="bottom" constant="7" id="FBY-7w-ZZz"/>
|
||||
<constraint firstItem="02U-BY-b7a" firstAttribute="firstBaseline" secondItem="Wai-eu-jJp" secondAttribute="firstBaseline" id="FQl-bS-Fk3"/>
|
||||
<constraint firstItem="I7A-fd-pwi" firstAttribute="top" secondItem="pv8-bs-3wb" secondAttribute="bottom" constant="8" id="KH6-H3-6wb"/>
|
||||
<constraint firstItem="pv8-bs-3wb" firstAttribute="centerY" secondItem="el3-VK-r6t" secondAttribute="centerY" id="LfK-hl-HxZ"/>
|
||||
<constraint firstItem="pv8-bs-3wb" firstAttribute="leading" secondItem="el3-VK-r6t" secondAttribute="trailing" constant="8" symbolic="YES" id="SD9-74-HqO"/>
|
||||
<constraint firstAttribute="trailing" secondItem="02U-BY-b7a" secondAttribute="trailing" constant="20" symbolic="YES" id="VzJ-zb-5QT"/>
|
||||
<constraint firstItem="QWb-zc-x4f" firstAttribute="leading" secondItem="I7A-fd-pwi" secondAttribute="trailing" constant="8" symbolic="YES" id="Vzq-9x-tFf"/>
|
||||
<constraint firstItem="02U-BY-b7a" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Wai-eu-jJp" secondAttribute="trailing" constant="8" symbolic="YES" id="Yjh-mv-hS2"/>
|
||||
<constraint firstItem="Wai-eu-jJp" firstAttribute="top" secondItem="YTq-d7-IZe" secondAttribute="bottom" id="ZFP-EZ-zMe"/>
|
||||
<constraint firstItem="QWb-zc-x4f" firstAttribute="leading" secondItem="YTq-d7-IZe" secondAttribute="trailing" constant="8" symbolic="YES" id="a51-cq-Mpe"/>
|
||||
<constraint firstItem="el3-VK-r6t" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="20" symbolic="YES" id="dYO-Ry-nTC"/>
|
||||
<constraint firstItem="YTq-d7-IZe" firstAttribute="leading" secondItem="I7A-fd-pwi" secondAttribute="leading" id="iv8-ta-QKu"/>
|
||||
<constraint firstItem="YTq-d7-IZe" firstAttribute="leading" secondItem="Wai-eu-jJp" secondAttribute="leading" id="knm-S7-Wnd"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="authorLabel" destination="Wai-eu-jJp" id="25m-9w-Q54"/>
|
||||
<outlet property="contentLabel" destination="YTq-d7-IZe" id="Kbg-hX-11B"/>
|
||||
<outlet property="dateLabel" destination="02U-BY-b7a" id="aRG-cY-3pe"/>
|
||||
<outlet property="feedImageView" destination="el3-VK-r6t" id="6PP-TY-Z1u"/>
|
||||
<outlet property="feedLabel" destination="pv8-bs-3wb" id="XEb-j4-JNA"/>
|
||||
<outlet property="thumbnailImageView" destination="QWb-zc-x4f" id="OKC-XS-7wv"/>
|
||||
<outlet property="titleLabel" destination="I7A-fd-pwi" id="9Ic-sM-Ypk"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="33.600000000000001" y="85.457271364317847"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
4
clients/ios/Widget Extension/en.lproj/InfoPlist.strings
Normal file
4
clients/ios/Widget Extension/en.lproj/InfoPlist.strings
Normal file
|
@ -0,0 +1,4 @@
|
|||
/* Localized versions of Info.plist keys */
|
||||
|
||||
CFBundleName = "NewsBlur";
|
||||
"NewsBlur Latest" = "NewsBlur";
|
Loading…
Add table
Reference in a new issue