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