Fjalapeno has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/220964

Change subject: Add save button to article card
......................................................................

Add save button to article card

Bug T103088

Adding the capability for the article VC to save unsafe an article.
Updating the list based on the using KVO when the saved list changes.

Transition needs updated to handle minimizing an article no longer in the saved 
pages list (See T103909)

Change-Id: I85aabdd98facd6a6a7a304328c0323d046243f52
---
M MediaWikiKit/MediaWikiKit/MWKSavedPageList.h
M MediaWikiKit/MediaWikiKit/MWKSavedPageList.m
M Wikipedia.xcodeproj/project.pbxproj
M Wikipedia/UI-V5/WMFAppViewController.m
M Wikipedia/UI-V5/WMFArticleListCollectionViewController.h
M Wikipedia/UI-V5/WMFArticleListCollectionViewController.m
M Wikipedia/UI-V5/WMFArticleListDataSource.h
M Wikipedia/UI-V5/WMFArticleViewController.h
M Wikipedia/UI-V5/WMFArticleViewController.m
M Wikipedia/UI-V5/WMFSavedPagesDataSource.h
M Wikipedia/UI-V5/WMFSavedPagesDataSource.m
M Wikipedia/UI-V5/WMFSearchViewController.h
M Wikipedia/UI-V5/WMFSearchViewController.m
M Wikipedia/UI-V5/WebViewController.storyboard
M Wikipedia/UI-V5/iPhone_Root.storyboard
15 files changed, 297 insertions(+), 72 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/ios/wikipedia 
refs/changes/64/220964/1

diff --git a/MediaWikiKit/MediaWikiKit/MWKSavedPageList.h 
b/MediaWikiKit/MediaWikiKit/MWKSavedPageList.h
index 03fb9e1..5c8b162 100644
--- a/MediaWikiKit/MediaWikiKit/MWKSavedPageList.h
+++ b/MediaWikiKit/MediaWikiKit/MWKSavedPageList.h
@@ -6,6 +6,11 @@
 
 @interface MWKSavedPageList : MWKDataObject <NSFastEnumeration>
 
+/**
+ *  Exposed for KVO only, do not manipulate directly
+ */
+@property (nonatomic, strong, readonly) NSMutableArray* entries;
+
 @property (readonly, weak, nonatomic) MWKDataStore* dataStore;
 @property (readonly, nonatomic, assign) NSUInteger length;
 @property (readonly, nonatomic, assign) BOOL dirty;
@@ -69,4 +74,5 @@
  */
 - (AnyPromise*)save;
 
+
 @end
diff --git a/MediaWikiKit/MediaWikiKit/MWKSavedPageList.m 
b/MediaWikiKit/MediaWikiKit/MWKSavedPageList.m
index 58e0e5c..8cf01a9 100644
--- a/MediaWikiKit/MediaWikiKit/MWKSavedPageList.m
+++ b/MediaWikiKit/MediaWikiKit/MWKSavedPageList.m
@@ -6,7 +6,7 @@
 @interface MWKSavedPageList ()
 
 @property (readwrite, weak, nonatomic) MWKDataStore* dataStore;
-@property (nonatomic, strong) NSMutableArray* entries;
+@property (nonatomic, strong, readwrite) NSMutableArray* entries;
 @property (nonatomic, strong) NSMutableDictionary* entriesByTitle;
 @property (readwrite, nonatomic, assign) BOOL dirty;
 
@@ -20,9 +20,10 @@
     self = [super init];
     if (self) {
         self.dataStore      = dataStore;
-        self.entries        = [[NSMutableArray alloc] init];
-        self.entriesByTitle = [[NSMutableDictionary alloc] init];
-        NSDictionary* data = [self.dataStore historyListData];
+        self.entries        = [NSMutableArray array];
+        self.entriesByTitle = [NSMutableDictionary dictionary];
+        NSDictionary* data = [self.dataStore savedPageListData];
+
         [self importData:data];
     }
     return self;
@@ -43,7 +44,7 @@
     if (arr) {
         for (NSDictionary* entryDict in arr) {
             MWKSavedPageEntry* entry = [[MWKSavedPageEntry alloc] 
initWithDict:entryDict];
-            [self.entries addObject:entry];
+            [[self mutableArrayValueForKey:@"entries"] addObject:entry];
             self.entriesByTitle[entry.title] = entry;
         }
     }
@@ -63,11 +64,11 @@
 #pragma mark - Entry Access
 
 - (NSUInteger)length {
-    return [self.entries count];
+    return [self countOfEntries];
 }
 
 - (MWKSavedPageEntry*)entryAtIndex:(NSUInteger)index {
-    return self.entries[index];
+    return [self objectInEntriesAtIndex:index];
 }
 
 - (MWKSavedPageEntry*)entryForTitle:(MWKTitle*)title {
@@ -81,7 +82,7 @@
 }
 
 - (NSUInteger)indexForEntry:(MWKHistoryEntry*)entry {
-    return [self.entries indexOfObject:entry];
+    return [[self mutableArrayValueForKey:@"entries"] indexOfObject:entry];
 }
 
 #pragma mark - Update Methods
@@ -99,13 +100,16 @@
     if (entry.title == nil) {
         return [AnyPromise promiseWithValue:[NSError 
wmf_errorWithType:WMFErrorTypeStringMissingParameter userInfo:nil]];
     }
+    NSDate* accessDate = entry.date ? entry.date : [NSDate date];
+
     MWKSavedPageEntry* oldEntry = [self entryForTitle:entry.title];
     if (oldEntry) {
-        [self.entries removeObject:oldEntry];
+        [[self entries] removeObject:oldEntry];
+        entry = oldEntry;
     }
 
-    entry.date = entry.date ? entry.date : [NSDate date];
-    [self.entries insertObject:entry atIndex:0];
+    entry.date = accessDate;
+    [[self mutableArrayValueForKey:@"entries"] insertObject:entry atIndex:0];
     self.entriesByTitle[entry.title] = entry;
     self.dirty                       = YES;
 
@@ -120,7 +124,7 @@
     MWKSavedPageEntry* entry = [self entryForTitle:title];
 
     if (entry) {
-        [self.entries removeObject:entry];
+        [[self mutableArrayValueForKey:@"entries"] removeObject:entry];
         [self.entriesByTitle removeObjectForKey:entry.title];
         self.dirty = YES;
     }
@@ -129,7 +133,7 @@
 }
 
 - (AnyPromise*)removeAllSavedPages {
-    [self.entries removeAllObjects];
+    [[self mutableArrayValueForKey:@"entries"] removeAllObjects];
     [self.entriesByTitle removeAllObjects];
     self.dirty = YES;
 
@@ -150,4 +154,42 @@
     return [AnyPromise promiseWithValue:nil];
 }
 
+- (NSUInteger)countOfEntries {
+    return [[self entries] count];
+}
+
+//- (void)getEntries:(id *)buffer range:(NSRange)inRange
+//{
+//    [[self entries] getObjects:buffer range:inRange];
+//}
+
+
+- (id)objectInEntriesAtIndex:(NSUInteger)idx {
+    return [[self entries] objectAtIndex:idx];
+}
+
+- (void)insertObject:(id)anObject inEntriesAtIndex:(NSUInteger)idx {
+    [[self entries] insertObject:anObject atIndex:idx];
+}
+
+- (void)insertEntries:(NSArray*)entrieArray atIndexes:(NSIndexSet*)indexes {
+    [[self entries] insertObjects:entrieArray atIndexes:indexes];
+}
+
+- (void)removeObjectFromEntriesAtIndex:(NSUInteger)idx {
+    [[self entries] removeObjectAtIndex:idx];
+}
+
+- (void)removeEntriesAtIndexes:(NSIndexSet*)indexes {
+    [[self entries] removeObjectsAtIndexes:indexes];
+}
+
+- (void)replaceObjectInEntriesAtIndex:(NSUInteger)idx withObject:(id)anObject {
+    [[self entries] replaceObjectAtIndex:idx withObject:anObject];
+}
+
+- (void)replaceEntriesAtIndexes:(NSIndexSet*)indexes 
withEntries:(NSArray*)entrieArray {
+    [[self entries] replaceObjectsAtIndexes:indexes withObjects:entrieArray];
+}
+
 @end
diff --git a/Wikipedia.xcodeproj/project.pbxproj 
b/Wikipedia.xcodeproj/project.pbxproj
index 30633d4..9762cbe 100644
--- a/Wikipedia.xcodeproj/project.pbxproj
+++ b/Wikipedia.xcodeproj/project.pbxproj
@@ -516,8 +516,8 @@
                04649CA619F72B360071E8FA /* libPods.a */ = {isa = 
PBXFileReference; lastKnownFileType = archive.ar; name = libPods.a; path = 
"Pods/build/Debug-iphoneos/libPods.a"; sourceTree = "<group>"; };
                046A4B9B1B38DC5400440F67 /* UIView+WMFRTLMirroring.h */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path 
= "UIView+WMFRTLMirroring.h"; sourceTree = "<group>"; };
                046A4B9C1B38DC5400440F67 /* UIView+WMFRTLMirroring.m */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
path = "UIView+WMFRTLMirroring.m"; sourceTree = "<group>"; };
-               046E2B691B31ED94008A99A6 /* UIButton+WMFButton.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
"UIButton+WMFButton.h"; sourceTree = "<group>"; };
-               046E2B6A1B31ED94008A99A6 /* UIButton+WMFButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= "UIButton+WMFButton.m"; sourceTree = "<group>"; };
+               046E2B691B31ED94008A99A6 /* UIButton+WMFButton.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
lineEnding = 0; path = "UIButton+WMFButton.h"; sourceTree = "<group>"; 
xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
+               046E2B6A1B31ED94008A99A6 /* UIButton+WMFButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
lineEnding = 0; path = "UIButton+WMFButton.m"; sourceTree = "<group>"; 
xcLanguageSpecificationIdentifier = xcode.lang.objc; };
                046E2B6D1B3213BE008A99A6 /* 
UIBarButtonItem+WMFButtonConvenience.h */ = {isa = PBXFileReference; 
fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
"UIBarButtonItem+WMFButtonConvenience.h"; sourceTree = "<group>"; };
                046E2B6E1B3213BE008A99A6 /* 
UIBarButtonItem+WMFButtonConvenience.m */ = {isa = PBXFileReference; 
fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = 
"UIBarButtonItem+WMFButtonConvenience.m"; sourceTree = "<group>"; };
                0472BC16193AD88C00C40BDA /* MWKSection+DisplayHtml.h */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path 
= "MWKSection+DisplayHtml.h"; sourceTree = "<group>"; };
@@ -1983,8 +1983,12 @@
                0E2B06FA1B2D129B00EA2F53 /* Common */ = {
                        isa = PBXGroup;
                        children = (
+                               046A4B9B1B38DC5400440F67 /* 
UIView+WMFRTLMirroring.h */,
+                               046A4B9C1B38DC5400440F67 /* 
UIView+WMFRTLMirroring.m */,
                                0ED44D761B28DA4D00F284BA /* 
UICollectionView+WMFExtensions.h */,
                                0ED44D771B28DA4D00F284BA /* 
UICollectionView+WMFExtensions.m */,
+                               04016E181B3264C700D732FE /* 
UIViewController+WMFStoryboardUtilities.h */,
+                               04016E191B3264C700D732FE /* 
UIViewController+WMFStoryboardUtilities.m */,
                        );
                        name = Common;
                        sourceTree = "<group>";
@@ -2041,47 +2045,21 @@
                        isa = PBXGroup;
                        children = (
                                0E94AFE91B209721000BC5EA /* 
iPhone_Root.storyboard */,
+                               0EFB48091B3BB01300381F99 /* Style */,
                                0E94AFEB1B20976A000BC5EA /* 
WMFAppViewController.h */,
                                0E94AFEC1B20976A000BC5EA /* 
WMFAppViewController.m */,
                                0E366B491B308A2600ABFB86 /* 
UIStoryboard+WMFExtensions.h */,
                                0E366B4A1B308A2600ABFB86 /* 
UIStoryboard+WMFExtensions.m */,
                                0E2B06F81B2D127A00EA2F53 /* Article List */,
                                0E2B06F91B2D128D00EA2F53 /* Search */,
-                               0E94AFF41B209882000BC5EA /* 
WMFArticleViewController.h */,
-                               0E94AFF51B209882000BC5EA /* 
WMFArticleViewController.m */,
-                               0E94AFF91B20A22C000BC5EA /* WMFStyleManager.h 
*/,
-                               0E94AFFA1B20A22C000BC5EA /* WMFStyleManager.m 
*/,
+                               0EFB48071B3BAFE900381F99 /* Article */,
                                0EFB0EF31B31DE7200D05C08 /* 
NSError+WMFExtensions.h */,
                                0EFB0EF41B31DE7200D05C08 /* 
NSError+WMFExtensions.m */,
                                0E2B06FA1B2D129B00EA2F53 /* Common */,
                                0E2B06F71B2D126700EA2F53 /* Data Sources */,
-                               04AEF0EF1B2E87A800EFE858 /* 
WebViewController.storyboard */,
-                               04AEF0F31B2E8CA100EFE858 /* 
SectionEditorViewController.storyboard */,
-                               042258551B34A29800FDD0C6 /* 
PreviewAndSaveViewController.storyboard */,
-                               042258571B34A2C100FDD0C6 /* 
EditSummaryViewController.storyboard */,
-                               04AEF0F51B2E8F6300EFE858 /* 
ReferencesVC.storyboard */,
-                               04AEF0FA1B2F703700EFE858 /* 
PrimaryMenuViewController.storyboard */,
-                               04DA876D1B2FEA7000C948F8 /* 
SearchResultsController.storyboard */,
-                               04DA87741B30A03600C948F8 /* 
LoginViewController.storyboard */,
-                               04DA876F1B2FF5B200C948F8 /* 
SecondaryMenuViewController.storyboard */,
-                               04DA87761B30A9D600C948F8 /* 
AccountCreationViewController.storyboard */,
-                               04DA87781B30B99300C948F8 /* 
CaptchaViewController.storyboard */,
-                               04DA877A1B30D29800C948F8 /* 
NearbyViewController.storyboard */,
-                               04DA877C1B30D29800C948F8 /* 
SavedPagesViewController.storyboard */,
-                               04DA87801B30E0C600C948F8 /* 
HistoryViewController.storyboard */,
-                               04016E1B1B3285B700D732FE /* 
AboutViewController.storyboard */,
-                               04016E1D1B328E8B00D732FE /* 
OnboardingViewController.storyboard */,
-                               04016E1F1B329D4500D732FE /* 
PageHistoryViewController.storyboard */,
-                               046E2B691B31ED94008A99A6 /* 
UIButton+WMFButton.h */,
-                               046E2B6A1B31ED94008A99A6 /* 
UIButton+WMFButton.m */,
-                               046E2B6D1B3213BE008A99A6 /* 
UIBarButtonItem+WMFButtonConvenience.h */,
-                               046E2B6E1B3213BE008A99A6 /* 
UIBarButtonItem+WMFButtonConvenience.m */,
-                               04016E181B3264C700D732FE /* 
UIViewController+WMFStoryboardUtilities.h */,
-                               04016E191B3264C700D732FE /* 
UIViewController+WMFStoryboardUtilities.m */,
+                               0EFB48081B3BAFF600381F99 /* Storyboards */,
                                04016E221B335F0200D732FE /* 
WMFArticlePresenter.h */,
                                04016E231B335F0200D732FE /* 
WMFArticlePresenter.m */,
-                               046A4B9B1B38DC5400440F67 /* 
UIView+WMFRTLMirroring.h */,
-                               046A4B9C1B38DC5400440F67 /* 
UIView+WMFRTLMirroring.m */,
                        );
                        path = "UI-V5";
                        sourceTree = "<group>";
@@ -2129,6 +2107,52 @@
                        path = Categories;
                        sourceTree = "<group>";
                };
+               0EFB48071B3BAFE900381F99 /* Article */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0E94AFF41B209882000BC5EA /* 
WMFArticleViewController.h */,
+                               0E94AFF51B209882000BC5EA /* 
WMFArticleViewController.m */,
+                       );
+                       name = Article;
+                       sourceTree = "<group>";
+               };
+               0EFB48081B3BAFF600381F99 /* Storyboards */ = {
+                       isa = PBXGroup;
+                       children = (
+                               04AEF0EF1B2E87A800EFE858 /* 
WebViewController.storyboard */,
+                               04AEF0F31B2E8CA100EFE858 /* 
SectionEditorViewController.storyboard */,
+                               042258551B34A29800FDD0C6 /* 
PreviewAndSaveViewController.storyboard */,
+                               042258571B34A2C100FDD0C6 /* 
EditSummaryViewController.storyboard */,
+                               04AEF0F51B2E8F6300EFE858 /* 
ReferencesVC.storyboard */,
+                               04AEF0FA1B2F703700EFE858 /* 
PrimaryMenuViewController.storyboard */,
+                               04DA876D1B2FEA7000C948F8 /* 
SearchResultsController.storyboard */,
+                               04DA87741B30A03600C948F8 /* 
LoginViewController.storyboard */,
+                               04DA876F1B2FF5B200C948F8 /* 
SecondaryMenuViewController.storyboard */,
+                               04DA87761B30A9D600C948F8 /* 
AccountCreationViewController.storyboard */,
+                               04DA87781B30B99300C948F8 /* 
CaptchaViewController.storyboard */,
+                               04DA877A1B30D29800C948F8 /* 
NearbyViewController.storyboard */,
+                               04DA877C1B30D29800C948F8 /* 
SavedPagesViewController.storyboard */,
+                               04DA87801B30E0C600C948F8 /* 
HistoryViewController.storyboard */,
+                               04016E1B1B3285B700D732FE /* 
AboutViewController.storyboard */,
+                               04016E1D1B328E8B00D732FE /* 
OnboardingViewController.storyboard */,
+                               04016E1F1B329D4500D732FE /* 
PageHistoryViewController.storyboard */,
+                       );
+                       name = Storyboards;
+                       sourceTree = "<group>";
+               };
+               0EFB48091B3BB01300381F99 /* Style */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0E94AFF91B20A22C000BC5EA /* WMFStyleManager.h 
*/,
+                               0E94AFFA1B20A22C000BC5EA /* WMFStyleManager.m 
*/,
+                               046E2B691B31ED94008A99A6 /* 
UIButton+WMFButton.h */,
+                               046E2B6A1B31ED94008A99A6 /* 
UIButton+WMFButton.m */,
+                               046E2B6D1B3213BE008A99A6 /* 
UIBarButtonItem+WMFButtonConvenience.h */,
+                               046E2B6E1B3213BE008A99A6 /* 
UIBarButtonItem+WMFButtonConvenience.m */,
+                       );
+                       name = Style;
+                       sourceTree = "<group>";
+               };
                BC69C3101AB0C16B0090B039 /* View Model */ = {
                        isa = PBXGroup;
                        children = (
diff --git a/Wikipedia/UI-V5/WMFAppViewController.m 
b/Wikipedia/UI-V5/WMFAppViewController.m
index e41a918..5a41c1b 100644
--- a/Wikipedia/UI-V5/WMFAppViewController.m
+++ b/Wikipedia/UI-V5/WMFAppViewController.m
@@ -50,11 +50,14 @@
 }
 
 - (void)loadMainUI {
-    [self updateListViewBasedOnSearchState:self.searchViewController.state];
+    self.searchViewController.searchSite    = [self.session searchSite];
+    self.searchViewController.dataStore     = self.session.dataStore;
+    self.searchViewController.userDataStore = self.session.userDataStore;
 
-    self.searchViewController.searchSite = [self.session searchSite];
-    self.searchViewController.dataStore  = [self.session dataStore];
-    self.listViewController.dataSource   = [[WMFSavedPagesDataSource alloc] 
initWithUserDataStore:[self userDataStore]];;
+    self.listViewController.savedPages = 
self.session.userDataStore.savedPageList;
+    self.listViewController.dataSource = [[WMFSavedPagesDataSource alloc] 
initWithSavedPagesList:[self userDataStore].savedPageList];
+
+    [self updateListViewBasedOnSearchState:self.searchViewController.state];
 }
 
 - (void)resumeApp {
diff --git a/Wikipedia/UI-V5/WMFArticleListCollectionViewController.h 
b/Wikipedia/UI-V5/WMFArticleListCollectionViewController.h
index 57d2c25..ae940a7 100644
--- a/Wikipedia/UI-V5/WMFArticleListCollectionViewController.h
+++ b/Wikipedia/UI-V5/WMFArticleListCollectionViewController.h
@@ -11,6 +11,7 @@
 
 @interface WMFArticleListCollectionViewController : UICollectionViewController
 
+@property (nonatomic, strong) MWKSavedPageList* savedPages;
 @property (nonatomic, strong, nullable) id<WMFArticleListDataSource> 
dataSource;
 
 @property (nonatomic, assign, readonly) WMFArticleListMode mode;
diff --git a/Wikipedia/UI-V5/WMFArticleListCollectionViewController.m 
b/Wikipedia/UI-V5/WMFArticleListCollectionViewController.m
index 7e2df06..6e0b334 100644
--- a/Wikipedia/UI-V5/WMFArticleListCollectionViewController.m
+++ b/Wikipedia/UI-V5/WMFArticleListCollectionViewController.m
@@ -12,6 +12,12 @@
 
 #import "UIViewController+WMFStoryboardUtilities.h"
 
+NSArray* indexPathsWithIndexSet(NSIndexSet* indexes, NSInteger section) {
+    return [indexes bk_mapIndex:^id (NSUInteger index) {
+        return [NSIndexPath indexPathForRow:(NSInteger)index 
inSection:section];
+    }];
+}
+
 @interface WMFArticleListCollectionViewController ()<TGLStackedLayoutDelegate>
 
 @property (nonatomic, assign, readwrite) WMFArticleListMode mode;
@@ -30,13 +36,20 @@
         return;
     }
 
+    [self unobserveDataSource];
     _dataSource = dataSource;
+    [self observeDataSource];
 
     self.title = [_dataSource displayTitle];
 
     if ([self isViewLoaded]) {
         [self.collectionView reloadData];
     }
+}
+
+- (void)setSavedPages:(MWKSavedPageList* __nonnull)savedPages {
+    _savedPages = savedPages;
+    [self.collectionView reloadData];
 }
 
 #pragma mark - List Mode
@@ -199,8 +212,8 @@
 
     [self addChildViewController:cell.viewController];
 
-    MWKArticle* article = [self.dataSource articleForIndexPath:indexPath];
-    cell.viewController.article = article;
+    cell.viewController.savedPages = self.savedPages;
+    cell.viewController.article    = [self.dataSource 
articleForIndexPath:indexPath];;
 
     return cell;
 }
@@ -222,6 +235,7 @@
     WMFArticleViewControllerContainerCell* cell = 
(WMFArticleViewControllerContainerCell*)[collectionView 
cellForItemAtIndexPath:indexPath];
 
     WMFArticleViewController* vc = [self.storyboard 
instantiateViewControllerWithIdentifier:NSStringFromClass([WMFArticleViewController
 class])];
+    vc.savedPages      = self.savedPages;
     vc.article         = cell.viewController.article;
     vc.contentTopInset = 64.0;
 
@@ -248,8 +262,59 @@
 
 - (void)stackLayout:(TGLStackedLayout*)layout 
deleteItemAtIndexPath:(NSIndexPath*)indexPath {
     if ([self.dataSource 
respondsToSelector:@selector(deleteArticleAtIndexPath:)]) {
+        [self unobserveDataSource];
         [self.dataSource deleteArticleAtIndexPath:indexPath];
+        [self observeDataSource];
     }
 }
 
+#pragma mark - DataSource KVO
+
+- (void)observeDataSource {
+    [self.KVOController observe:_dataSource keyPath:@"articles" options:0 
block:^(id observer, id object, NSDictionary* change) {
+        NSKeyValueChange changeKind = [change[NSKeyValueChangeKindKey] 
unsignedIntegerValue];
+        NSArray* indexPaths = 
indexPathsWithIndexSet(change[NSKeyValueChangeIndexesKey], 0);
+        [self updateCellsAtIndexPaths:indexPaths change:changeKind];
+    }];
+}
+
+- (void)unobserveDataSource {
+    [self.KVOController unobserve:_dataSource];
+}
+
+#pragma mark - Process DataSource Changes
+
+- (void)updateCellsAtIndexPaths:(NSArray*)indexPaths 
change:(NSKeyValueChange)change {
+    [self.collectionView performBatchUpdates:^{
+        switch (change) {
+            case NSKeyValueChangeInsertion:
+                [self insertCellsAtIndexPaths:indexPaths];
+                break;
+            case NSKeyValueChangeRemoval:
+                [self deleteCellsAtIndexPaths:indexPaths];
+                break;
+            case NSKeyValueChangeReplacement:
+                [self reloadCellsAtIndexPaths:indexPaths];
+                break;
+            case NSKeyValueChangeSetting:
+                [self.collectionView reloadData];
+                break;
+            default:
+                break;
+        }
+    } completion:NULL];
+}
+
+- (void)insertCellsAtIndexPaths:(NSArray*)indexPaths {
+    [self.collectionView insertItemsAtIndexPaths:indexPaths];
+}
+
+- (void)deleteCellsAtIndexPaths:(NSArray*)indexPaths {
+    [self.collectionView deleteItemsAtIndexPaths:indexPaths];
+}
+
+- (void)reloadCellsAtIndexPaths:(NSArray*)indexPaths {
+    [self.collectionView reloadItemsAtIndexPaths:indexPaths];
+}
+
 @end
diff --git a/Wikipedia/UI-V5/WMFArticleListDataSource.h 
b/Wikipedia/UI-V5/WMFArticleListDataSource.h
index a469ffe..8651036 100644
--- a/Wikipedia/UI-V5/WMFArticleListDataSource.h
+++ b/Wikipedia/UI-V5/WMFArticleListDataSource.h
@@ -7,6 +7,8 @@
 
 - (nullable NSString*)displayTitle;
 
+@property (nonatomic, strong, readonly) NSArray* articles;
+
 - (NSUInteger) articleCount;
 - (MWKArticle*)articleForIndexPath:(NSIndexPath*)indexPath;
 
diff --git a/Wikipedia/UI-V5/WMFArticleViewController.h 
b/Wikipedia/UI-V5/WMFArticleViewController.h
index 4754d79..56c41c7 100644
--- a/Wikipedia/UI-V5/WMFArticleViewController.h
+++ b/Wikipedia/UI-V5/WMFArticleViewController.h
@@ -5,6 +5,7 @@
 
 @property (nonatomic, assign) CGFloat contentTopInset;
 
+@property (nonatomic, strong) MWKSavedPageList* savedPages;
 @property (nonatomic, strong) MWKArticle* article;
 
 @end
diff --git a/Wikipedia/UI-V5/WMFArticleViewController.m 
b/Wikipedia/UI-V5/WMFArticleViewController.m
index 2e656eb..bde8510 100644
--- a/Wikipedia/UI-V5/WMFArticleViewController.m
+++ b/Wikipedia/UI-V5/WMFArticleViewController.m
@@ -2,11 +2,15 @@
 #import "WMFArticleViewController.h"
 #import <Masonry/Masonry.h>
 #import "WMFArticlePresenter.h"
+#import "Wikipedia-Swift.h"
+#import "PromiseKit.h"
+#import "UIButton+WMFButton.h"
 
 @interface WMFArticleViewController ()
 
 @property (strong, nonatomic) IBOutlet UIView* cardBackgroundView;
 @property (strong, nonatomic) IBOutlet UILabel* titleLabel;
+@property (strong, nonatomic) IBOutlet UIButton* saveButton;
 
 @end
 
@@ -54,6 +58,7 @@
 
 - (void)viewDidLoad {
     [super viewDidLoad];
+    [self.saveButton wmf_setButtonType:WMFButtonTypeHeart];
     self.view.backgroundColor = [UIColor clearColor];
     [self updateContentForTopInset];
     [self updateUIAnimated:NO];
@@ -65,6 +70,25 @@
 
 - (void)updateUIAnimated:(BOOL)animated {
     self.titleLabel.text = self.article.title.text;
+    [self updateSavedButtonState];
+}
+
+- (IBAction)toggleSave:(id)sender {
+    dispatch_promise_on(dispatch_get_main_queue(), ^{
+        if ([self.savedPages isSaved:self.article.title]) {
+            return [self.savedPages 
removeSavedPageWithTitle:self.article.title];
+        } else {
+            return [self.savedPages savePageWithTitle:self.article.title];
+        }
+    }).thenOn(dispatch_get_main_queue(), ^(){
+        return [self.savedPages save];
+    }).thenOn(dispatch_get_main_queue(), ^(){
+        [self updateSavedButtonState];
+    });
+}
+
+- (void)updateSavedButtonState {
+    self.saveButton.selected = [self.savedPages isSaved:self.article.title];
 }
 
 @end
diff --git a/Wikipedia/UI-V5/WMFSavedPagesDataSource.h 
b/Wikipedia/UI-V5/WMFSavedPagesDataSource.h
index 9ff71c0..40cef80 100644
--- a/Wikipedia/UI-V5/WMFSavedPagesDataSource.h
+++ b/Wikipedia/UI-V5/WMFSavedPagesDataSource.h
@@ -6,9 +6,14 @@
 
 @interface WMFSavedPagesDataSource : MTLModel<WMFArticleListDataSource>
 
-@property (nonatomic, strong, readonly) MWKUserDataStore* userDataStore;
+/**
+ *  Observable
+ */
+@property (nonatomic, strong, readonly) NSArray* articles;
 
-- (nonnull instancetype)initWithUserDataStore:(MWKUserDataStore*)store;
+@property (nonatomic, strong, readonly) MWKSavedPageList* savedPages;
+
+- (nonnull instancetype)initWithSavedPagesList:(MWKSavedPageList*)savedPages;
 
 @end
 
diff --git a/Wikipedia/UI-V5/WMFSavedPagesDataSource.m 
b/Wikipedia/UI-V5/WMFSavedPagesDataSource.m
index 069ec0f..b4234c8 100644
--- a/Wikipedia/UI-V5/WMFSavedPagesDataSource.m
+++ b/Wikipedia/UI-V5/WMFSavedPagesDataSource.m
@@ -12,18 +12,41 @@
 
 @interface WMFSavedPagesDataSource ()
 
-@property (nonatomic, strong, readwrite) MWKUserDataStore* userDataStore;
+@property (nonatomic, strong, readwrite) MWKSavedPageList* savedPages;
 
 @end
 
 @implementation WMFSavedPagesDataSource
 
-- (instancetype)initWithUserDataStore:(MWKUserDataStore*)store {
+- (nonnull instancetype)initWithSavedPagesList:(MWKSavedPageList*)savedPages {
     self = [super init];
     if (self) {
-        self.userDataStore = store;
+        self.savedPages = savedPages;
+        [self.KVOController observe:savedPages keyPath:@"entries" 
options:NSKeyValueObservingOptionPrior block:^(id observer, id object, 
NSDictionary* change) {
+            NSKeyValueChange changeKind = [change[NSKeyValueChangeKindKey] 
unsignedIntegerValue];
+
+            if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
+                [self willChange:changeKind 
valuesAtIndexes:change[NSKeyValueChangeIndexesKey] forKey:@"articles"];
+            } else {
+                [self didChange:changeKind 
valuesAtIndexes:change[NSKeyValueChangeIndexesKey] forKey:@"articles"];
+            }
+        }];
     }
     return self;
+}
+
+- (NSArray*)articles {
+    return [[self.savedPages entries] bk_map:^id (id obj) {
+        return [self articleForEntry:obj];
+    }];
+}
+
+- (MWKArticle*)articleForEntry:(MWKSavedPageEntry*)entry {
+    return [[self dataStore] articleWithTitle:entry.title];
+}
+
+- (MWKDataStore*)dataStore {
+    return self.savedPages.dataStore;
 }
 
 - (nullable NSString*)displayTitle {
@@ -34,10 +57,6 @@
     return [[self savedPages] length];
 }
 
-- (MWKSavedPageList*)savedPages {
-    return [self.userDataStore savedPageList];
-}
-
 - (MWKSavedPageEntry*)savedPageForIndexPath:(NSIndexPath*)indexPath {
     MWKSavedPageEntry* savedEntry = [self.savedPages 
entryAtIndex:indexPath.row];
     return savedEntry;
@@ -45,7 +64,7 @@
 
 - (MWKArticle*)articleForIndexPath:(NSIndexPath*)indexPath {
     MWKSavedPageEntry* savedEntry = [self savedPageForIndexPath:indexPath];
-    return [self.userDataStore.dataStore articleWithTitle:savedEntry.title];
+    return [self articleForEntry:savedEntry];
 }
 
 - (BOOL)canDeleteItemAtIndexpath:(NSIndexPath*)indexPath {
@@ -55,8 +74,8 @@
 - (void)deleteArticleAtIndexPath:(NSIndexPath*)indexPath {
     MWKSavedPageEntry* savedEntry = [self savedPageForIndexPath:indexPath];
     if (savedEntry) {
-        [[self savedPages] removeSavedPageWithTitle:savedEntry.title].then(^{
-            [[self savedPages] save];
+        [self.savedPages removeSavedPageWithTitle:savedEntry.title].then(^{
+            return [self.savedPages save];
         });
     }
 }
diff --git a/Wikipedia/UI-V5/WMFSearchViewController.h 
b/Wikipedia/UI-V5/WMFSearchViewController.h
index 6dea657..8e5aa64 100644
--- a/Wikipedia/UI-V5/WMFSearchViewController.h
+++ b/Wikipedia/UI-V5/WMFSearchViewController.h
@@ -12,6 +12,7 @@
 
 @property (nonatomic, strong) MWKSite* searchSite;
 @property (nonatomic, strong) MWKDataStore* dataStore;
+@property (nonatomic, strong) MWKUserDataStore* userDataStore;
 
 @property(nonatomic, weak, nullable) id<WMFSearchViewControllerDelegate> 
delegate;
 
diff --git a/Wikipedia/UI-V5/WMFSearchViewController.m 
b/Wikipedia/UI-V5/WMFSearchViewController.m
index a7d0e43..0a025e1 100644
--- a/Wikipedia/UI-V5/WMFSearchViewController.m
+++ b/Wikipedia/UI-V5/WMFSearchViewController.m
@@ -31,6 +31,11 @@
 
 @implementation WMFSearchViewController
 
+- (void)setUserDataStore:(MWKUserDataStore* __nonnull)userDataStore {
+    _userDataStore                        = userDataStore;
+    self.resultsListController.savedPages = _userDataStore.savedPageList;
+}
+
 - (NSString*)currentSearchTerm {
     return [(WMFSearchResults*)self.resultsListController.dataSource 
searchTerm];
 }
diff --git a/Wikipedia/UI-V5/WebViewController.storyboard 
b/Wikipedia/UI-V5/WebViewController.storyboard
index a09caa4..4f7438f 100644
--- a/Wikipedia/UI-V5/WebViewController.storyboard
+++ b/Wikipedia/UI-V5/WebViewController.storyboard
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" 
version="3.0" toolsVersion="7702" systemVersion="14D136" 
targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" 
initialViewController="vXZ-lx-hvc">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" 
version="3.0" toolsVersion="7706" systemVersion="15A204h" 
targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" 
initialViewController="vXZ-lx-hvc">
     <dependencies>
         <deployment identifier="iOS"/>
         <development version="5100" identifier="xcode"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" 
version="7701"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" 
version="7703"/>
     </dependencies>
     <scenes>
         <!--Table of Contents-->
@@ -20,9 +20,11 @@
                         <subviews>
                             <scrollView contentMode="scaleToFill" 
alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" 
translatesAutoresizingMaskIntoConstraints="NO" id="sgu-W1-m18" userLabel="TOC 
Scroll View">
                                 <rect key="frame" x="0.0" y="0.0" width="320" 
height="504"/>
+                                <animations/>
                                 <color key="backgroundColor" 
red="0.047058828175067902" green="0.047058828175067902" 
blue="0.047058828175067902" alpha="1" colorSpace="deviceRGB"/>
                             </scrollView>
                         </subviews>
+                        <animations/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" 
colorSpace="calibratedWhite"/>
                         <constraints>
                             <constraint firstItem="sgu-W1-m18" 
firstAttribute="top" secondItem="PkH-FW-CMU" secondAttribute="top" 
id="2Fv-ke-Rdf"/>
@@ -55,6 +57,7 @@
                         <subviews>
                             <webView clipsSubviews="YES" 
contentMode="scaleToFill" allowsInlineMediaPlayback="NO" 
translatesAutoresizingMaskIntoConstraints="NO" id="WeL-Mj-Zsh">
                                 <rect key="frame" x="0.0" y="20" width="320" 
height="548"/>
+                                <animations/>
                                 <color key="backgroundColor" white="1" 
alpha="1" colorSpace="calibratedWhite"/>
                                 <dataDetectorType key="dataDetectorTypes"/>
                                 <connections>
@@ -63,6 +66,7 @@
                             </webView>
                             <label clipsSubviews="YES" 
userInteractionEnabled="NO" alpha="0.93000000000000005" contentMode="left" 
horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" 
textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" 
baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" 
preferredMaxLayoutWidth="320" translatesAutoresizingMaskIntoConstraints="NO" 
id="aCV-ih-PXn" userLabel="Zero Status Label" customClass="PaddedLabel">
                                 <rect key="frame" x="0.0" y="528" width="320" 
height="40"/>
+                                <animations/>
                                 <color key="backgroundColor" white="0.0" 
alpha="1" colorSpace="calibratedWhite"/>
                                 <constraints>
                                     <constraint firstAttribute="height" 
constant="40" placeholder="YES" id="JYC-aG-aKe"/>
@@ -73,6 +77,7 @@
                             </label>
                             <containerView clipsSubviews="YES" 
clearsContextBeforeDrawing="NO" contentMode="scaleToFill" 
translatesAutoresizingMaskIntoConstraints="NO" id="gB8-UC-wuQ" 
userLabel="References Container View">
                                 <rect key="frame" x="0.0" y="368" width="320" 
height="200"/>
+                                <animations/>
                                 <color key="backgroundColor" white="1" 
alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
                                 <constraints>
                                     <constraint firstAttribute="height" 
constant="200" id="Z6r-VJ-hik"/>
@@ -80,6 +85,7 @@
                             </containerView>
                             <containerView opaque="NO" clipsSubviews="YES" 
contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" 
id="2Hk-lZ-HoX" userLabel="TOC Container View">
                                 <rect key="frame" x="320" y="20" width="160" 
height="548"/>
+                                <animations/>
                                 <constraints>
                                     <constraint firstAttribute="width" 
constant="160" id="WUm-Ge-3SR"/>
                                 </constraints>
@@ -88,6 +94,7 @@
                                 </connections>
                             </containerView>
                         </subviews>
+                        <animations/>
                         <color key="backgroundColor" red="1" green="1" 
blue="1" alpha="1" colorSpace="calibratedRGB"/>
                         <constraints>
                             <constraint firstItem="2Hk-lZ-HoX" 
firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="trailing" 
id="2dq-Qh-1Z6"/>
diff --git a/Wikipedia/UI-V5/iPhone_Root.storyboard 
b/Wikipedia/UI-V5/iPhone_Root.storyboard
index 4bc94e4..2a2011f 100644
--- a/Wikipedia/UI-V5/iPhone_Root.storyboard
+++ b/Wikipedia/UI-V5/iPhone_Root.storyboard
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" 
version="3.0" toolsVersion="7702" systemVersion="14D136" 
targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" 
useTraitCollections="YES" initialViewController="O8j-Xm-zXE">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" 
version="3.0" toolsVersion="7706" systemVersion="15A204h" 
targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" 
useTraitCollections="YES" initialViewController="O8j-Xm-zXE">
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" 
version="7701"/>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" 
version="7703"/>
         <capability name="Constraints to layout margins" 
minToolsVersion="6.0"/>
     </dependencies>
     <scenes>
@@ -217,15 +218,16 @@
                                     <view contentMode="scaleToFill" 
translatesAutoresizingMaskIntoConstraints="NO" id="rdC-bu-Efj">
                                         <rect key="frame" x="2" y="2" 
width="596" height="576"/>
                                         <subviews>
-                                            <label opaque="NO" 
userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" 
verticalHuggingPriority="251" text="Title" lineBreakMode="tailTruncation" 
baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" 
translatesAutoresizingMaskIntoConstraints="NO" id="Myt-29-qy0">
-                                                <rect key="frame" x="282" 
y="8" width="33" height="21"/>
+                                            <label opaque="NO" 
userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" 
misplaced="YES" text="Title" lineBreakMode="tailTruncation" numberOfLines="0" 
baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" 
translatesAutoresizingMaskIntoConstraints="NO" id="Myt-29-qy0">
+                                                <rect key="frame" x="8" y="8" 
width="538" height="20.5"/>
                                                 <animations/>
                                                 <fontDescription 
key="fontDescription" type="system" pointSize="17"/>
-                                                <color key="textColor" 
cocoaTouchSystemColor="darkTextColor"/>
+                                                <color key="textColor" 
red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                                                 <nil key="highlightedColor"/>
                                             </label>
                                             <button opaque="NO" 
contentMode="scaleToFill" contentHorizontalAlignment="center" 
contentVerticalAlignment="center" buttonType="roundedRect" 
lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" 
id="Llu-j4-JGU">
-                                                <rect key="frame" x="280" 
y="45" width="36" height="30"/>
+                                                <rect key="frame" x="280" 
y="44" width="36" height="30"/>
+                                                <animations/>
                                                 <state key="normal" 
title="Read">
                                                     <color key="titleColor" 
white="1" alpha="1" colorSpace="calibratedWhite"/>
                                                     <color 
key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
@@ -234,18 +236,35 @@
                                                     <action 
selector="readButtonTapped:" destination="r3R-dd-kNR" eventType="touchUpInside" 
id="GhA-xA-JnH"/>
                                                 </connections>
                                             </button>
+                                            <button opaque="NO" 
contentMode="scaleToFill" horizontalHuggingPriority="251" 
horizontalCompressionResistancePriority="751" misplaced="YES" 
contentHorizontalAlignment="center" contentVerticalAlignment="center" 
lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" 
id="6NH-F8-mBf">
+                                                <rect key="frame" x="554" 
y="4" width="34" height="30"/>
+                                                <animations/>
+                                                <state key="normal">
+                                                    <color 
key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+                                                </state>
+                                                <connections>
+                                                    <action 
selector="toggleSave:" destination="r3R-dd-kNR" eventType="touchUpInside" 
id="E1S-HV-Euy"/>
+                                                </connections>
+                                            </button>
                                         </subviews>
                                         <animations/>
-                                        <color key="backgroundColor" 
red="0.91030293699999998" green="0.12721729279999999" 
blue="0.28739702700000003" alpha="1" colorSpace="custom" 
customColorSpace="sRGB"/>
+                                        <color key="backgroundColor" 
red="0.7065587043762207" green="0.77467125654220581" blue="0.87251251935958862" 
alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                         <constraints>
                                             <constraint firstItem="Llu-j4-JGU" 
firstAttribute="top" secondItem="Myt-29-qy0" secondAttribute="bottom" 
constant="16" id="7gg-ju-ska"/>
                                             <constraint 
firstAttribute="centerX" secondItem="Llu-j4-JGU" secondAttribute="centerX" 
id="9QY-5K-otN"/>
                                             <constraint firstItem="Myt-29-qy0" 
firstAttribute="top" secondItem="rdC-bu-Efj" secondAttribute="top" constant="8" 
id="BpY-2f-f2T"/>
+                                            <constraint firstItem="6NH-F8-mBf" 
firstAttribute="leading" secondItem="Myt-29-qy0" secondAttribute="trailing" 
constant="8" id="JxC-1Y-MeC"/>
                                             <constraint 
firstAttribute="centerX" secondItem="Myt-29-qy0" secondAttribute="centerX" 
id="Ldy-Zr-x9q"/>
+                                            <constraint firstItem="Myt-29-qy0" 
firstAttribute="leading" secondItem="rdC-bu-Efj" secondAttribute="leading" 
constant="8" id="SsY-Eq-bMN"/>
+                                            <constraint firstItem="6NH-F8-mBf" 
firstAttribute="centerY" secondItem="Myt-29-qy0" secondAttribute="centerY" 
id="ata-PV-p1y"/>
+                                            <constraint firstItem="Myt-29-qy0" 
firstAttribute="centerY" secondItem="6NH-F8-mBf" secondAttribute="centerY" 
constant="-2" id="bH9-mZ-qft"/>
                                             <constraint 
firstAttribute="centerX" secondItem="Myt-29-qy0" secondAttribute="centerX" 
id="pMk-fk-lzv"/>
+                                            <constraint 
firstAttribute="trailing" secondItem="6NH-F8-mBf" secondAttribute="trailing" 
constant="8" id="xlM-WU-9vG"/>
                                         </constraints>
                                         <variation key="default">
                                             <mask key="constraints">
+                                                <exclude 
reference="Ldy-Zr-x9q"/>
+                                                <exclude 
reference="bH9-mZ-qft"/>
                                                 <exclude 
reference="pMk-fk-lzv"/>
                                             </mask>
                                         </variation>
@@ -278,12 +297,13 @@
                     </view>
                     <connections>
                         <outlet property="cardBackgroundView" 
destination="oM7-J8-i1e" id="9wA-Gd-1Tj"/>
+                        <outlet property="saveButton" destination="6NH-F8-mBf" 
id="Vvf-QS-mCo"/>
                         <outlet property="titleLabel" destination="Myt-29-qy0" 
id="hXQ-Sv-N1d"/>
                     </connections>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" 
id="hIj-Na-3vK" userLabel="First Responder" sceneMemberID="firstResponder"/>
             </objects>
-            <point key="canvasLocation" x="2605" y="333"/>
+            <point key="canvasLocation" x="2577" y="357"/>
         </scene>
     </scenes>
     <resources>

-- 
To view, visit https://gerrit.wikimedia.org/r/220964
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I85aabdd98facd6a6a7a304328c0323d046243f52
Gerrit-PatchSet: 1
Gerrit-Project: apps/ios/wikipedia
Gerrit-Branch: 5.0
Gerrit-Owner: Fjalapeno <cfl...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to