iOS: #1160 (state restoration)

- Added a "Restore position" preference in the "Reading Stories" group, with options to restore the previous state on launch always, within a specified interval, or never.
- When within the indicated interval, the app restores the feeds, feed detail, and story selections and scrolling positions.
- The active story is marked unread to ensure it remains available for restoring, and re-marked read when resumed.
- NOTE: only supported on iPhone for now.  I’ll add iPad support later.
This commit is contained in:
David Sinclair 2019-03-22 20:55:22 -07:00 committed by Samuel Clay
parent f10a002d46
commit ed8d3c76e6
13 changed files with 275 additions and 37 deletions

View file

@ -44,6 +44,8 @@
@interface FeedDetailViewController ()
@property (nonatomic) NSUInteger scrollingMarkReadRow;
@property (nonatomic, strong) NSString *restoringFolder;
@property (nonatomic, strong) NSString *restoringFeedID;
@end
@ -82,7 +84,9 @@
selector:@selector(preferredContentSizeChanged:)
name:UIContentSizeCategoryDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(finishedLoadingFeedsNotification:) name:@"FinishedLoadingFeedsNotification" object:nil];
self.storyTitlesTable.backgroundColor = UIColorFromRGB(0xf4f4f4);
self.storyTitlesTable.separatorColor = UIColorFromRGB(0xE9E8E4);
if (@available(iOS 11.0, *)) {
@ -528,6 +532,57 @@
return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone || self.appDelegate.isCompactWidth;
}
#pragma mark -
#pragma mark State Restoration
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeObject:appDelegate.storiesCollection.activeFolder forKey:@"folder"];
if (appDelegate.storiesCollection.activeFeed != nil) {
[coder encodeObject:[NSString stringWithFormat:@"%@", appDelegate.storiesCollection.activeFeed[@"id"]] forKey:@"feed_id"];
}
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
NSString *folder = [coder decodeObjectOfClass:[NSString class] forKey:@"folder"];
NSString *feedID = [coder decodeObjectOfClass:[NSString class] forKey:@"feed_id"];
if (folder != nil || feedID != nil) {
self.restoringFolder = folder;
self.restoringFeedID = feedID;
}
}
- (void)finishedLoadingFeedsNotification:(NSNotification *)notification {
if (self.restoringFeedID.length > 0) {
NSDictionary *feed = [appDelegate getFeed:self.restoringFeedID];
if (feed != nil) {
appDelegate.storiesCollection.isSocialView = NO;
appDelegate.storiesCollection.activeFeed = feed;
[appDelegate loadFeedDetailView:NO];
[self viewWillAppear:NO];
}
} else if (self.restoringFolder.length > 0) {
NSString *folder = self.restoringFolder;
NSInteger index = [appDelegate.dictFoldersArray indexOfObject:folder];
if (index != NSNotFound && index > NewsBlurTopSectionAllStories) {
folder = [NSString stringWithFormat:@"%@", @(index)];
}
[appDelegate loadRiverFeedDetailView:self withFolder:folder];
[self viewWillAppear:NO];
}
self.restoringFolder = nil;
self.restoringFeedID = 0;
}
#pragma mark -
#pragma mark Initialization

View file

@ -321,7 +321,19 @@
self.storyPageControl.navigationItem.titleView = titleLabel;
}
# pragma mark Modals and Popovers
#pragma mark -
#pragma mark State Restoration
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
}
#pragma mark -
#pragma mark Modals and Popovers
- (void)showUserProfilePopover:(id)sender {
if ([sender class] == [InteractionCell class] ||

View file

@ -79,6 +79,8 @@
#define CURRENT_DB_VERSION 36
#define CURRENT_STATE_VERSION 1
@synthesize window;
@synthesize ftuxNavigationController;
@ -193,7 +195,7 @@
return (NewsBlurAppDelegate*) [UIApplication sharedApplication].delegate;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self registerDefaultsFromSettingsBundle];
self.navigationController.delegate = self;
@ -242,6 +244,10 @@
// Uncomment below line to test image caching
// [[NSURLCache sharedURLCache] removeAllCachedResponses];
return YES;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([UIApplicationShortcutItem class] && launchOptions[UIApplicationLaunchOptionsShortcutItemKey]) {
self.launchedShortcutItem = launchOptions[UIApplicationLaunchOptionsShortcutItemKey];
return NO;
@ -262,6 +268,12 @@
[self handleShortcutItem:self.launchedShortcutItem];
self.launchedShortcutItem = nil;
}
if (storyPageControl.temporarilyMarkedUnread && [storiesCollection isStoryUnread:activeStory]) {
[storiesCollection markStoryRead:activeStory];
[storiesCollection syncStoryAsRead:activeStory];
storyPageControl.temporarilyMarkedUnread = NO;
}
}
- (void)applicationWillResignActive:(UIApplication *)application {
@ -276,6 +288,75 @@
[self.feedsViewController refreshHeaderCounts];
}
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder {
// Temporary? bypass to not save on iPad, since that isn't supported yet.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return NO;
}
return YES;
}
- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
[coder encodeInteger:CURRENT_STATE_VERSION forKey:@"version"];
[coder encodeObject:[NSDate date] forKey:@"last_saved_state_date"];
}
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder {
// Temporary? bypass to not restore on iPad, since that isn't supported yet.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return NO;
}
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSString *option = [preferences stringForKey:@"restore_state"];
if ([option isEqualToString:@"never"]) {
return NO;
} else if ([option isEqualToString:@"always"]) {
return YES;
}
NSTimeInterval daysInterval = 60 * 60;
NSTimeInterval limitInterval = option.doubleValue * daysInterval;
NSInteger version = [coder decodeIntegerForKey:@"version"];
NSDate *lastSavedDate = [coder decodeObjectOfClass:[NSDate class] forKey:@"last_saved_state_date"];
if (limitInterval == 0) {
limitInterval = 24 * daysInterval;
}
if (version > CURRENT_STATE_VERSION || lastSavedDate == nil) {
return NO;
}
NSTimeInterval savedInterval = -[lastSavedDate timeIntervalSinceNow];
return savedInterval < limitInterval;
}
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
NSString *identifier = identifierComponents.lastObject;
if ([identifier isEqualToString:@"MainNavigation"]) {
return self.navigationController;
} else if ([identifier isEqualToString:@"FeedsView"]) {
return self.feedsViewController;
} else if ([identifier isEqualToString:@"FeedDetailView"]) {
return self.feedDetailViewController;
} else if ([identifier isEqualToString:@"StoryPageControl"]) {
return self.storyPageControl;
} else if ([identifier isEqualToString:@"ContainerView"]) {
return self.masterContainerViewController;
} else {
return nil;
}
}
- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
// All done; could do any cleanup here
}
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
completionHandler([self handleShortcutItem:shortcutItem]);
}
@ -1538,7 +1619,7 @@
[self.folderCountCache removeObjectForKey:feedDetailView.storiesCollection.activeFolder];
}
if (feedDetailView == feedDetailViewController) {
if (feedDetailView == feedDetailViewController && feedDetailView.navigationController == nil) {
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle: @"All"
style: UIBarButtonItemStylePlain
target: nil

View file

@ -444,6 +444,17 @@ static NSArray<NSString *> *NewsBlurTopSectionNames;
return YES;
}
#pragma mark -
#pragma mark State Restoration
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
}
#pragma mark -
#pragma mark Initialization

View file

@ -176,7 +176,7 @@
- (NSInteger)indexOfActiveStory {
for (NSInteger i=0; i < self.storyCount; i++) {
NSDictionary *story = [activeFeedStories objectAtIndex:i];
if ([appDelegate.activeStory objectForKey:@"story_hash"] == [story objectForKey:@"story_hash"]) {
if ([[appDelegate.activeStory objectForKey:@"story_hash"] isEqualToString:[story objectForKey:@"story_hash"]]) {
return i;
}
}
@ -186,7 +186,7 @@
- (NSInteger)indexOfStoryId:(id)storyId {
for (int i=0; i < self.storyCount; i++) {
NSDictionary *story = [activeFeedStories objectAtIndex:i];
if ([story objectForKey:@"story_hash"] == storyId) {
if ([[story objectForKey:@"story_hash"] isEqualToString:storyId]) {
return i;
}
}
@ -195,7 +195,7 @@
- (NSInteger)locationOfStoryId:(id)storyId {
for (int i=0; i < [activeFeedStoryLocations count]; i++) {
if ([activeFeedStoryLocationIds objectAtIndex:i] == storyId) {
if ([[activeFeedStoryLocationIds objectAtIndex:i] isEqualToString:storyId]) {
return i;
}
}

View file

@ -429,6 +429,10 @@
// NSLog(@"Already drawn story, drawing anyway: %@", [self.activeStory objectForKey:@"story_title"]);
// return;
}
if (self.activeStory == nil) {
return;
}
scrollPct = 0;
hasScrolled = NO;

View file

@ -89,6 +89,7 @@
@property (nonatomic) BOOL forceNavigationBarShown;
@property (nonatomic) BOOL currentlyTogglingNavigationBar;
@property (nonatomic, readonly) BOOL isHorizontal;
@property (nonatomic) BOOL temporarilyMarkedUnread;
- (void)resizeScrollView;
- (void)applyNewIndex:(NSInteger)newIndex pageController:(StoryDetailViewController *)pageController;

View file

@ -28,6 +28,7 @@
@property (nonatomic, strong) NSTimer *autoscrollTimer;
@property (nonatomic, strong) NSTimer *autoscrollViewTimer;
@property (nonatomic, strong) NSString *restoringStoryId;
@end
@ -231,7 +232,7 @@
context:nil];
_orientation = [UIApplication sharedApplication].statusBarOrientation;
[self addKeyCommandWithInput:UIKeyInputDownArrow modifierFlags:0 action:@selector(changeToNextPage:) discoverabilityTitle:@"Next Story"];
[self addKeyCommandWithInput:@"j" modifierFlags:0 action:@selector(changeToNextPage:) discoverabilityTitle:@"Next Story"];
[self addKeyCommandWithInput:UIKeyInputUpArrow modifierFlags:0 action:@selector(changeToPreviousPage:) discoverabilityTitle:@"Previous Story"];
@ -761,6 +762,38 @@
return YES;
}
#pragma mark -
#pragma mark State Restoration
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeObject:currentPage.activeStoryId forKey:@"current_story_id"];
[appDelegate.storiesCollection toggleStoryUnread];
self.temporarilyMarkedUnread = YES;
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
self.restoringStoryId = [coder decodeObjectOfClass:[NSString class] forKey:@"current_story_id"];
}
- (void)restorePage {
if (self.restoringStoryId.length > 0) {
NSInteger pageIndex = [appDelegate.storiesCollection indexOfStoryId:self.restoringStoryId];
if (pageIndex < 0) {
[self doNextUnreadStory:nil];
} else {
[self changePage:pageIndex animated:NO];
}
self.restoringStoryId = nil;
}
}
#pragma mark -
#pragma mark Side scroll view
@ -1166,6 +1199,11 @@
}
- (void)advanceToNextUnread {
if (self.restoringStoryId.length > 0) {
[self restorePage];
return;
}
if (!self.waitingForNextUnreadFromServer) {
return;
}

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" colorMatched="YES">
<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>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -44,7 +44,7 @@
<outlet property="window" destination="12" id="78"/>
</connections>
</customObject>
<window opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="12" customClass="EventWindow">
<window opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" restorationIdentifier="EventWindow" id="12" customClass="EventWindow">
<rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -114,7 +114,7 @@
<outlet property="appDelegate" destination="3" id="279"/>
</connections>
</viewController>
<viewController nibName="FeedDetailViewController" id="51" customClass="FeedDetailViewController">
<viewController restorationIdentifier="FeedDetailView" nibName="FeedDetailViewController" id="51" customClass="FeedDetailViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
<connections>
@ -150,7 +150,7 @@
<outlet property="appDelegate" destination="3" id="288"/>
</connections>
</viewController>
<viewController nibName="NewsBlurViewController" id="10" customClass="NewsBlurViewController">
<viewController restorationIdentifier="FeedsView" nibName="NewsBlurViewController" id="10" customClass="NewsBlurViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
@ -165,14 +165,14 @@
<outlet property="appDelegate" destination="3" id="271"/>
</connections>
</viewController>
<viewController id="290" customClass="StoryPageControl">
<viewController restorationIdentifier="StoryPageControl" id="290" customClass="StoryPageControl">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
<connections>
<outlet property="appDelegate" destination="3" id="291"/>
</connections>
</viewController>
<viewController nibName="StoryDetailViewController" id="92" customClass="StoryDetailViewController">
<viewController restorationIdentifier="StoryDetailView" nibName="StoryDetailViewController" id="92" customClass="StoryDetailViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
<connections>
@ -186,18 +186,18 @@
<outlet property="appDelegate" destination="3" id="237"/>
</connections>
</viewController>
<viewController title="Main Container View Controller" id="264" customClass="NBContainerViewController">
<viewController restorationIdentifier="ContainerView" title="Main Container View Controller" id="264" customClass="NBContainerViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
<connections>
<outlet property="appDelegate" destination="3" id="267"/>
</connections>
</viewController>
<navigationController title="Main" id="173">
<navigationController restorationIdentifier="MainNavigation" title="Main" id="173">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
<navigationBar key="navigationBar" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="174">
<rect key="frame" x="0.0" y="20" width="768" height="44"/>
<rect key="frame" x="0.0" y="-50" width="768" height="50"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<viewControllers>

View file

@ -1,13 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina5_5" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
<capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -21,11 +19,11 @@
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="1">
<view contentMode="scaleToFill" restorationIdentifier="FeedDetailView" id="1">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" bouncesZoom="NO" style="plain" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="4">
<tableView clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="FeedDetailTable" showsHorizontalScrollIndicator="NO" bouncesZoom="NO" style="plain" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="4">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<inset key="separatorInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
@ -44,7 +42,7 @@
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" alpha="0.40000000596046448" contentMode="scaleToFill" image="big_world.png" translatesAutoresizingMaskIntoConstraints="NO" id="Kdh-D5-esJ">
<rect key="frame" x="175.33333333333334" y="24" width="64" height="64"/>
<rect key="frame" x="175" y="24" width="64" height="64"/>
<constraints>
<constraint firstAttribute="height" constant="64" id="1c2-mE-46C"/>
<constraint firstAttribute="width" constant="64" id="Odo-if-TOJ"/>

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -42,7 +42,7 @@
<outlet property="window" destination="12" id="78"/>
</connections>
</customObject>
<viewController nibName="NewsBlurViewController" id="10" customClass="NewsBlurViewController">
<viewController restorationIdentifier="FeedsView" nibName="NewsBlurViewController" id="10" customClass="NewsBlurViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<connections>
@ -61,19 +61,19 @@
<outlet property="appDelegate" destination="3" id="166"/>
</connections>
</viewController>
<viewController nibName="FeedDetailViewController" id="51" customClass="FeedDetailViewController">
<viewController restorationIdentifier="FeedDetailView" nibName="FeedDetailViewController" id="51" customClass="FeedDetailViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<connections>
<outlet property="appDelegate" destination="3" id="58"/>
</connections>
</viewController>
<viewController nibName="StoryDetailViewController" id="92" customClass="StoryDetailViewController">
<viewController restorationIdentifier="StoryDetailView" nibName="StoryDetailViewController" id="92" customClass="StoryDetailViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<connections>
<outlet property="appDelegate" destination="3" id="105"/>
</connections>
</viewController>
<viewController nibName="StoryPageControl" automaticallyAdjustsScrollViewInsets="NO" id="170" customClass="StoryPageControl">
<viewController restorationIdentifier="StoryPageControl" nibName="StoryPageControl" automaticallyAdjustsScrollViewInsets="NO" id="170" customClass="StoryPageControl">
<extendedEdge key="edgesForExtendedLayout"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<connections>
@ -162,22 +162,22 @@
<outlet property="appDelegate" destination="3" id="158"/>
</connections>
</viewController>
<viewController id="177" customClass="DashboardViewController">
<viewController restorationIdentifier="DashboardView" id="177" customClass="DashboardViewController">
<extendedEdge key="edgesForExtendedLayout"/>
<connections>
<outlet property="appDelegate" destination="3" id="178"/>
</connections>
</viewController>
<window opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="12" customClass="EventWindow">
<window opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" restorationIdentifier="EventWindow" id="12" customClass="EventWindow">
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
</window>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="39">
<navigationController restorationIdentifier="MainNavigation" automaticallyAdjustsScrollViewInsets="NO" id="39">
<extendedEdge key="edgesForExtendedLayout"/>
<navigationBar key="navigationBar" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="41">
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
<rect key="frame" x="0.0" y="-44" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<viewControllers>

View file

@ -24,14 +24,14 @@
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="6">
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" restorationIdentifier="RootView" id="6">
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="85">
<rect key="frame" x="0.0" y="0.0" width="320" height="436"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" bouncesZoom="NO" style="plain" separatorStyle="none" rowHeight="6" estimatedRowHeight="6" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="8" userLabel="Table View">
<tableView clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="FeedTitlesTable" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" bouncesZoom="NO" style="plain" separatorStyle="none" rowHeight="6" estimatedRowHeight="6" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="8" userLabel="Table View">
<rect key="frame" x="0.0" y="0.0" width="320" height="436"/>
<color key="backgroundColor" cocoaTouchSystemColor="lightTextColor"/>
<connections>

View file

@ -700,6 +700,44 @@
<key>Key</key>
<string>story_browser</string>
</dict>
<dict>
<key>Type</key>
<string>PSMultiValueSpecifier</string>
<key>Title</key>
<string>Restore position</string>
<key>Titles</key>
<array>
<string>Always</string>
<string>Within 1 hour</string>
<string>Within 2 hours</string>
<string>Within 4 hours</string>
<string>Within 6 hours</string>
<string>Within 8 hours</string>
<string>Within 12 hours</string>
<string>Within 1 day</string>
<string>Within 2 days</string>
<string>Within 1 week</string>
<string>Never</string>
</array>
<key>DefaultValue</key>
<string>24</string>
<key>Values</key>
<array>
<string>always</string>
<string>1</string>
<string>2</string>
<string>4</string>
<string>6</string>
<string>8</string>
<string>12</string>
<string>24</string>
<string>48</string>
<string>168</string>
<string>never</string>
</array>
<key>Key</key>
<string>restore_state</string>
</dict>
<dict>
<key>Title</key>
<string>App badge</string>