Fjalapeno has uploaded a new change for review.

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

Change subject: Implement new refresh saved pages logic and UI.
......................................................................

Implement new refresh saved pages logic and UI.

Add progress bar to nav bar.
Add cancel button to nav bar.
Show informative alert when user first taps cancel refresh
Add saved pages fetch controller.
Make the article fetcher use background queue (otherwise blocks UI during 
refresh)
Add masonry pod for auto layout syntax.

Change-Id: I0d03cec85081738211bb120593cd2777ba8518d8
---
M Podfile
M Podfile.lock
M Wikipedia.xcodeproj/project.pbxproj
A wikipedia/Custom Views/WMFBorderButton.h
A wikipedia/Custom Views/WMFBorderButton.m
A wikipedia/Custom Views/WMFProgressLineView.h
A wikipedia/Custom Views/WMFProgressLineView.m
M wikipedia/Networking/Fetchers/ArticleFetcher.m
A wikipedia/Networking/Fetchers/SavedArticlesFetcher.h
A wikipedia/Networking/Fetchers/SavedArticlesFetcher.m
M wikipedia/View Controllers/Navigation/Top/TopMenuViewController.h
M wikipedia/View Controllers/Navigation/Top/TopMenuViewController.m
M wikipedia/View Controllers/SavedPages/SavedPagesViewController.h
M wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
M wikipedia/en.lproj/Localizable.strings
M wikipedia/qqq.lproj/Localizable.strings
16 files changed, 736 insertions(+), 88 deletions(-)


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

diff --git a/Podfile b/Podfile
index 28cadab..2e24612 100644
--- a/Podfile
+++ b/Podfile
@@ -4,6 +4,7 @@
 pod 'AFNetworking', '< 2.6'
 pod 'hpple', '< 0.3'
 pod 'blockskit/Core', '< 2.3'
+pod 'Masonry', '< 0.7'
 
 target 'WikipediaUnitTests', :exclusive => false do
   pod 'OCMockito', '< 1.5'
diff --git a/Podfile.lock b/Podfile.lock
index 3286146..c19da5e 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -22,6 +22,7 @@
     - AFNetworking/NSURLSession
   - BlocksKit/Core (2.2.5)
   - hpple (0.2.0)
+  - Masonry (0.6.1)
   - OCHamcrest (4.1.1)
   - OCMockito (1.4.0):
     - OCHamcrest (~> 4.0)
@@ -30,6 +31,7 @@
   - AFNetworking (< 2.6)
   - blockskit/Core (< 2.3)
   - hpple (< 0.3)
+  - Masonry (< 0.7)
   - OCHamcrest (< 4.2)
   - OCMockito (< 1.5)
 
@@ -37,6 +39,7 @@
   AFNetworking: 0f54cb5d16ce38c1b76948faffb8d5fb705021c7
   BlocksKit: 4439e7f30a9f90743462ca63545a15c1f4304cef
   hpple: f4eb7c21a8db83ec264e5d614ec7509e10e5adec
+  Masonry: 2cb49fd14d203d2db5ca6bb017135b235dde9980
   OCHamcrest: af1c7c5ea345de69ea6c9c2958f65f3044e5c420
   OCMockito: 991936bb775cc4c27f063d38f5e17b9161fbd21c
 
diff --git a/Wikipedia.xcodeproj/project.pbxproj 
b/Wikipedia.xcodeproj/project.pbxproj
index 379bc48..4fbf156 100644
--- a/Wikipedia.xcodeproj/project.pbxproj
+++ b/Wikipedia.xcodeproj/project.pbxproj
@@ -276,6 +276,9 @@
                BCB66A0C1A85183000C7B1FE /* NSString+WMFHTMLParsing.m in 
Sources */ = {isa = PBXBuildFile; fileRef = BCB66A0B1A85183000C7B1FE /* 
NSString+WMFHTMLParsing.m */; };
                BCB66A0D1A85183000C7B1FE /* NSString+WMFHTMLParsing.m in 
Sources */ = {isa = PBXBuildFile; fileRef = BCB66A0B1A85183000C7B1FE /* 
NSString+WMFHTMLParsing.m */; };
                BCB66A101A851C9B00C7B1FE /* MWKImageListTests.m in Sources */ = 
{isa = PBXBuildFile; fileRef = BCB66A0F1A851C9B00C7B1FE /* MWKImageListTests.m 
*/; };
+               C42D947E1A937DAC00A4871A /* SavedArticlesFetcher.m in Sources 
*/ = {isa = PBXBuildFile; fileRef = C42D947D1A937DAC00A4871A /* 
SavedArticlesFetcher.m */; };
+               C42D94861A937DE000A4871A /* WMFBorderButton.m in Sources */ = 
{isa = PBXBuildFile; fileRef = C42D94831A937DE000A4871A /* WMFBorderButton.m 
*/; };
+               C42D94871A937DE000A4871A /* WMFProgressLineView.m in Sources */ 
= {isa = PBXBuildFile; fileRef = C42D94851A937DE000A4871A /* 
WMFProgressLineView.m */; };
                C46FBA4B1A8530EE00C5730F /* Pods-acknowledgements.plist in 
Resources */ = {isa = PBXBuildFile; fileRef = C46FBA4A1A8530EE00C5730F /* 
Pods-acknowledgements.plist */; };
                C90799BA1A8564C60044E13C /* WMFShareOptionsViewController.m in 
Sources */ = {isa = PBXBuildFile; fileRef = C90799B91A8564C60044E13C /* 
WMFShareOptionsViewController.m */; };
                C9180EC418AED30C006C1DCA /* WikipediaAppUtils.m in Sources */ = 
{isa = PBXBuildFile; fileRef = C9180EC318AED30C006C1DCA /* WikipediaAppUtils.m 
*/; };
@@ -774,6 +777,12 @@
                BCB66A0A1A85183000C7B1FE /* NSString+WMFHTMLParsing.h */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path 
= "NSString+WMFHTMLParsing.h"; sourceTree = "<group>"; };
                BCB66A0B1A85183000C7B1FE /* NSString+WMFHTMLParsing.m */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
path = "NSString+WMFHTMLParsing.m"; sourceTree = "<group>"; };
                BCB66A0F1A851C9B00C7B1FE /* MWKImageListTests.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= MWKImageListTests.m; sourceTree = "<group>"; };
+               C42D947C1A937DAC00A4871A /* SavedArticlesFetcher.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
SavedArticlesFetcher.h; sourceTree = "<group>"; };
+               C42D947D1A937DAC00A4871A /* SavedArticlesFetcher.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= SavedArticlesFetcher.m; sourceTree = "<group>"; };
+               C42D94821A937DE000A4871A /* WMFBorderButton.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WMFBorderButton.h; sourceTree = "<group>"; };
+               C42D94831A937DE000A4871A /* WMFBorderButton.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WMFBorderButton.m; sourceTree = "<group>"; };
+               C42D94841A937DE000A4871A /* WMFProgressLineView.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WMFProgressLineView.h; sourceTree = "<group>"; };
+               C42D94851A937DE000A4871A /* WMFProgressLineView.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WMFProgressLineView.m; sourceTree = "<group>"; };
                C46FBA4A1A8530EE00C5730F /* Pods-acknowledgements.plist */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; 
name = "Pods-acknowledgements.plist"; path = "../../../Pods/Target Support 
Files/Pods/Pods-acknowledgements.plist"; sourceTree = "<group>"; };
                C90799B81A8564C60044E13C /* WMFShareOptionsViewController.h */ 
= {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.h; name = WMFShareOptionsViewController.h; path = 
ShareCard/WMFShareOptionsViewController.h; sourceTree = "<group>"; };
                C90799B91A8564C60044E13C /* WMFShareOptionsViewController.m */ 
= {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; name = WMFShareOptionsViewController.m; path = 
ShareCard/WMFShareOptionsViewController.m; sourceTree = "<group>"; };
@@ -1406,6 +1415,8 @@
                                0487045C19F8262600B7D307 /* AccountLogin.m */,
                                0487045D19F8262600B7D307 /* ArticleFetcher.h */,
                                0487045E19F8262600B7D307 /* ArticleFetcher.m */,
+                               C42D947C1A937DAC00A4871A /* 
SavedArticlesFetcher.h */,
+                               C42D947D1A937DAC00A4871A /* 
SavedArticlesFetcher.m */,
                                0487045F19F8262600B7D307 /* AssetsFileFetcher.h 
*/,
                                0487046019F8262600B7D307 /* AssetsFileFetcher.m 
*/,
                                0487046419F8262600B7D307 /* CaptchaResetter.h 
*/,
@@ -2000,6 +2011,17 @@
                        path = ../MediaWikiKit/MediaWikiKitTests;
                        sourceTree = "<group>";
                };
+               C42D94811A937DE000A4871A /* Custom Views */ = {
+                       isa = PBXGroup;
+                       children = (
+                               C42D94821A937DE000A4871A /* WMFBorderButton.h 
*/,
+                               C42D94831A937DE000A4871A /* WMFBorderButton.m 
*/,
+                               C42D94841A937DE000A4871A /* 
WMFProgressLineView.h */,
+                               C42D94851A937DE000A4871A /* 
WMFProgressLineView.m */,
+                       );
+                       path = "Custom Views";
+                       sourceTree = "<group>";
+               };
                C9180EC118AED30C006C1DCA /* mw-utils */ = {
                        isa = PBXGroup;
                        children = (
@@ -2102,6 +2124,7 @@
                                0463639518A844380049EE4F /* Keychain */,
                                04B60509193522650007185A /* MenuButton */,
                                04B6050D1935236C0007185A /* MenuLabel */,
+                               C42D94811A937DE000A4871A /* Custom Views */,
                                0487041519F824D700B7D307 /* Networking */,
                                048A26741906268100395F53 /* PaddedLabel */,
                                0447866C1852B5010050563B /* Session */,
@@ -2728,6 +2751,7 @@
                                045374881A35834D00CE1A56 /* 
LeadImageTitleAttributedString.m in Sources */,
                                04292FF2185FBA70002A13FC /* SearchResultCell.m 
in Sources */,
                                04F0E2EA186EDC1A00468738 /* 
UIWebView+ElementLocation.m in Sources */,
+                               C42D94871A937DE000A4871A /* 
WMFProgressLineView.m in Sources */,
                                0447866F1852B5010050563B /* SessionSingleton.m 
in Sources */,
                                BCB669AA1A83F6C400C7B1FE /* MWKImage.m in 
Sources */,
                                D4E8A8A4190835C100DA4765 /* DataMigrator.m in 
Sources */,
@@ -2762,6 +2786,7 @@
                                BCB669AE1A83F6C400C7B1FE /* MWKHistoryEntry.m 
in Sources */,
                                0433542618A093C5009305F0 /* 
UIView+RemoveConstraints.m in Sources */,
                                C98990341A699DE000AF44FC /* 
WMFShareCardViewController.m in Sources */,
+                               C42D94861A937DE000A4871A /* WMFBorderButton.m 
in Sources */,
                                047801BE18AE987900DBB747 /* 
UIButton+ColorMask.m in Sources */,
                                BC86B93D1A929CC500B4C039 /* 
UICollectionViewFlowLayout+NSCopying.m in Sources */,
                                0429300A18604898002A13FC /* 
SavedPagesResultCell.m in Sources */,
@@ -2835,6 +2860,7 @@
                                BCB669AC1A83F6C400C7B1FE /* MWKSavedPageEntry.m 
in Sources */,
                                049B640C18AAF36200D98BD4 /* 
UIViewController+SearchChildViewControllers.m in Sources */,
                                04A97E8718B81D5D0046B166 /* 
AccountCreationViewController.m in Sources */,
+                               C42D947E1A937DAC00A4871A /* 
SavedArticlesFetcher.m in Sources */,
                                04530AF81935C07500022512 /* 
ModalContentViewController.m in Sources */,
                                0487048219F8262600B7D307 /* AssetsFileFetcher.m 
in Sources */,
                                BC955BC71A82BEFD000EF9E4 /* 
MWKImageInfoFetcher.m in Sources */,
diff --git a/wikipedia/Custom Views/WMFBorderButton.h b/wikipedia/Custom 
Views/WMFBorderButton.h
new file mode 100644
index 0000000..46898c6
--- /dev/null
+++ b/wikipedia/Custom Views/WMFBorderButton.h
@@ -0,0 +1,30 @@
+
+#import <UIKit/UIKit.h>
+
+@interface WMFBorderButton : UIButton
+
+@property (nonatomic) CGFloat borderWidth;
+@property (nonatomic) CGFloat cornerRadius;
+@property (nonatomic, strong) UIColor* borderColor;
+
+/**
+ *  Create a bordered button
+ *
+ *  @param width  The width of the border
+ *  @param radius The corner radius of the border
+ *  @param color  The color of the border
+ *
+ *  @return A new bordered button
+ */
++ (WMFBorderButton *)buttonWithBorderWidth:(CGFloat)width 
cornerRadius:(CGFloat)radius color:(UIColor*)color;
+
+/**
+ *  Returns a button with default options for width, color, radius
+ *
+ *  @return A new default configured Button
+ */
++ (WMFBorderButton *)standardBorderButton;
+
+
+
+@end
diff --git a/wikipedia/Custom Views/WMFBorderButton.m b/wikipedia/Custom 
Views/WMFBorderButton.m
new file mode 100644
index 0000000..43043da
--- /dev/null
+++ b/wikipedia/Custom Views/WMFBorderButton.m
@@ -0,0 +1,84 @@
+
+#import "WMFBorderButton.h"
+#import <objc/objc-runtime.h>
+
+static CGFloat const kWMFBorderButtonWidthPadding = 20.0;
+static CGFloat const kWMFBorderButtonHeightPadding = 10.0;
+@implementation WMFBorderButton
+
+@dynamic borderWidth, cornerRadius;
+
+#pragma mark - Convienence
+
++ (WMFBorderButton *)standardBorderButton{
+    
+    return [WMFBorderButton buttonWithBorderWidth:1.0 cornerRadius:4.0 
color:[UIColor colorWithRed:0.051 green:0.482 blue:0.984 alpha:1]];
+}
+
++ (WMFBorderButton *)buttonWithBorderWidth:(CGFloat)width 
cornerRadius:(CGFloat)radius color:(UIColor*)color;
+{
+    WMFBorderButton* button = [WMFBorderButton 
buttonWithType:UIButtonTypeCustom];
+    button.layer.masksToBounds = YES;
+    button.borderWidth = width;
+    button.cornerRadius = radius;
+    button.borderColor = color;
+    [button setTitleColor:color forState:UIControlStateNormal];
+    [button.titleLabel setFont:[UIFont systemFontOfSize:14.0]];
+    [button setAdjustsImageWhenHighlighted:NO];
+    
+    return button;
+}
+
+#pragma mark - Runtime
+
+- (id)forwardingTargetForSelector:(SEL)aSelector{
+    
+    if(sel_isEqual(aSelector, @selector(setBorderWidth:)) ||
+       sel_isEqual(aSelector, @selector(borderWidth)) ||
+       sel_isEqual(aSelector, @selector(setCornerRadius:)) ||
+       sel_isEqual(aSelector, @selector(cornerRadius))
+       ){
+        
+        [self setNeedsDisplay];
+        [self setNeedsLayout];
+        [self setNeedsUpdateConstraints];
+        return self.layer;
+    }
+    
+    return [super forwardingTargetForSelector:aSelector];
+}
+
+#pragma mark - Accessors
+
+- (void)setBorderColor:(UIColor *)borderColor{
+    
+    self.layer.borderColor = borderColor.CGColor;
+    [self setNeedsDisplay];
+    [self setNeedsLayout];
+    [self setNeedsUpdateConstraints];
+}
+
+- (UIColor*)borderColor{
+    
+    return [UIColor colorWithCGColor:self.layer.borderColor];
+}
+
+#pragma mark - UIView
+
+- (void)sizeToFit{
+    
+    [super sizeToFit];
+    CGRect f = self.frame;
+    f.size.width += kWMFBorderButtonWidthPadding;
+    f.size.height += kWMFBorderButtonHeightPadding;
+}
+
+- (CGSize)intrinsicContentSize{
+    
+    CGSize s = [super intrinsicContentSize];
+    s.width += kWMFBorderButtonWidthPadding;
+    s.height += kWMFBorderButtonHeightPadding;
+    return s;
+}
+
+@end
diff --git a/wikipedia/Custom Views/WMFProgressLineView.h b/wikipedia/Custom 
Views/WMFProgressLineView.h
new file mode 100644
index 0000000..b839739
--- /dev/null
+++ b/wikipedia/Custom Views/WMFProgressLineView.h
@@ -0,0 +1,24 @@
+
+#import <UIKit/UIKit.h>
+
+@interface WMFProgressLineView : UIView
+
+
+/**
+ *  Set the color of the progress.
+ *  Default = [UIColor colorWithRed:0.106 green:0.678 blue:0.533 alpha:1]
+ *  (Default Background Color = [UIColor colorWithRed:0.071 green:0.514 
blue:0.404 alpha:1])
+ */
+@property (nonatomic, strong) UIColor* progressColor;
+
+/**
+ *  The current progress shown by the receiver.
+ *  The progress value ranges from 0 to 1. The default value is 0.
+ */
+@property (nonatomic, assign) float progress;
+
+- (void)setProgress:(float)progress animated:(BOOL)animated;
+
+- (void)setProgress:(float)progress animated:(BOOL)animated 
completion:(dispatch_block_t)completion;
+
+@end
diff --git a/wikipedia/Custom Views/WMFProgressLineView.m b/wikipedia/Custom 
Views/WMFProgressLineView.m
new file mode 100644
index 0000000..7658d47
--- /dev/null
+++ b/wikipedia/Custom Views/WMFProgressLineView.m
@@ -0,0 +1,94 @@
+
+
+#import "WMFProgressLineView.h"
+
+@interface WMFProgressLineView ()
+
+@property (nonatomic, strong) UIView *progressBar;
+
+@end
+
+@implementation WMFProgressLineView
+
+
+#pragma mark - UIView
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    if (self = [super initWithFrame:frame]) {
+        self.frame = frame;
+        self.clipsToBounds = YES;
+        self.backgroundColor = [UIColor clearColor];
+        self.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
+        self.progressBar = [[UIView alloc] init];
+        self.progressBar.backgroundColor = [UIColor colorWithRed:0.106 
green:0.682 blue:0.541 alpha:1];
+        self.progress = 0.0;
+        [self addSubview:self.progressBar];
+    }
+    return self;
+}
+
+- (void)setFrame:(CGRect)frame
+{
+    //whole pixels for non-retina screens
+    frame.origin.y = ceilf(frame.origin.y);
+    frame.size.height = floorf(frame.size.height);
+    [super setFrame:frame];
+    
+    __weak typeof(self)weakSelf = self;
+    dispatch_async(dispatch_get_main_queue(), ^{
+        weakSelf.progress = weakSelf.progress;
+    });
+}
+
+#pragma mark - Progress
+
+- (void)setProgressColor:(UIColor *)progressColor{
+    self.progressBar.backgroundColor = progressColor;
+}
+
+- (void)setProgress:(float)progress {
+    [self setProgress:progress animated:NO];
+}
+
+- (void)setProgress:(float)progress animated:(BOOL)animated{
+    
+    [self setProgress:progress animated:animated completion:NULL];
+}
+
+- (void)setProgress:(float)progress animated:(BOOL)animated 
completion:(dispatch_block_t)completion{
+    
+    _progress = (progress < 0) ? 0 :
+                               (progress > 1) ? 1 :
+                               progress;
+    
+    CGRect slice, remainder;
+    CGRectDivide(self.bounds, &slice, &remainder, CGRectGetWidth(self.bounds) 
* _progress, CGRectMinXEdge);
+
+    [CATransaction begin];
+    
+    [CATransaction setCompletionBlock:completion];
+
+    if (!CGRectEqualToRect(self.progressBar.frame, slice)) {
+        
+        
+        if(animated){
+            
+            [UIView animateWithDuration:0.2 animations:^{
+                
+                self.progressBar.frame = slice;
+            }];
+            
+        }else{
+            
+            self.progressBar.frame = slice;
+            
+        }
+    }
+    
+    [CATransaction commit];
+}
+
+
+
+@end
diff --git a/wikipedia/Networking/Fetchers/ArticleFetcher.m 
b/wikipedia/Networking/Fetchers/ArticleFetcher.m
index 8688929..d2e6c62 100644
--- a/wikipedia/Networking/Fetchers/ArticleFetcher.m
+++ b/wikipedia/Networking/Fetchers/ArticleFetcher.m
@@ -45,6 +45,7 @@
 
 -(void)fetchWithManager:(AFHTTPRequestOperationManager *)manager
 {
+    
     NSString *title = self.article.title.prefixedText;
     NSString *subdomain = self.article.title.site.language;
     
@@ -77,43 +78,56 @@
     [self addMCCMNCHeaderToRequestSerializer:manager.requestSerializer 
ifAppropriateForURL:url];
 
     [manager GET:url.absoluteString parameters:params 
success:^(AFHTTPRequestOperation *operation, id responseObject) {
-        //NSLog(@"JSON: %@", responseObject);
-        [[MWNetworkActivityIndicatorManager sharedManager] pop];
-
-        // Convert the raw NSData response to a dictionary.
-        responseObject = [self dictionaryFromDataResponse:responseObject];
-
-        // Clear any MCCMNC header - needed because manager is a singleton.
-        [self 
removeMCCMNCHeaderFromRequestSerializer:manager.requestSerializer];
         
-        //NSDictionary *leadSectionResults = [self 
prepareResultsFromResponse:responseObject forTitle:title];
-        if (self.article.needsRefresh) {
-            [self.article remove];
-        }
-        @try {
-            [self.article importMobileViewJSON:responseObject[@"mobileview"]];
+        __block id localResponseObject = responseObject;
+        
+        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 
0), ^{
+            
+            //NSLog(@"JSON: %@", responseObject);
+            [[MWNetworkActivityIndicatorManager sharedManager] pop];
+            
+            // Convert the raw NSData response to a dictionary.
+            localResponseObject = [self 
dictionaryFromDataResponse:localResponseObject];
+            
+            // Clear any MCCMNC header - needed because manager is a singleton.
+            [self 
removeMCCMNCHeaderFromRequestSerializer:manager.requestSerializer];
+            
+            //NSDictionary *leadSectionResults = [self 
prepareResultsFromResponse:responseObject forTitle:title];
+            if (self.article.needsRefresh) {
+                [self.article remove];
+            }
+            @try {
+                [self.article 
importMobileViewJSON:localResponseObject[@"mobileview"]];
+                [self.article save];
+            }
+            @catch (NSException *e) {
+                NSLog(@"%@", e);
+                NSError *err = [NSError errorWithDomain:@"ArticleFetcher" 
code:666 userInfo:@{@"exception": e}];
+                [self finishWithError: err
+                          fetchedData: nil];
+                return;
+            }
+            
+            //[self applyResultsForLeadSection:leadSectionResults];
+            for (int n = 0; n < [self.article.sections count]; n++) {
+                (void)self.article.sections[n].images; // hack
+                [self createImageRecordsForSection:n];
+            }
+            
+            [self associateThumbFromTempDirWithArticle];
+            
             [self.article save];
-        }
-        @catch (NSException *e) {
-            NSLog(@"%@", e);
-            NSError *err = [NSError errorWithDomain:@"ArticleFetcher" code:666 
userInfo:@{@"exception": e}];
-            [self finishWithError: err
-                      fetchedData: nil];
-            return;
-        }
+            
+            dispatch_async(dispatch_get_main_queue(), ^{
+                
+                [self finishWithError: nil
+                          fetchedData: nil];
+                
+            });
+    
+        });
         
-        //[self applyResultsForLeadSection:leadSectionResults];
-        for (int n = 0; n < [self.article.sections count]; n++) {
-            (void)self.article.sections[n].images; // hack
-            [self createImageRecordsForSection:n];
-        }
-
-        [self associateThumbFromTempDirWithArticle];
-
-        [self.article save];
-
-        [self finishWithError: nil
-                  fetchedData: nil];
+        
         
     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
         NSLog(@"Error: %@", error);
diff --git a/wikipedia/Networking/Fetchers/SavedArticlesFetcher.h 
b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.h
new file mode 100644
index 0000000..841ed2d
--- /dev/null
+++ b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.h
@@ -0,0 +1,30 @@
+
+#import "FetcherBase.h"
+
+@class MWKArticle, MWKSavedPageList, AFHTTPRequestOperationManager;
+@class SavedArticlesFetcher;
+
+@protocol SavedArticlesFetcherDelegate <FetchFinishedDelegate>
+
+- (void)savedArticlesFetcher: (SavedArticlesFetcher*)savedArticlesFetcher
+             didFetchArticle: (MWKArticle*)article
+           remainingArticles: (NSInteger)remaining
+               totalArticles: (NSInteger)total
+                      status: (FetchFinalStatus)status
+                       error: (NSError *)error;
+
+@end
+
+@interface SavedArticlesFetcher : FetcherBase
+
+@property (strong, nonatomic, readonly) MWKSavedPageList *savedPageList;
+@property (nonatomic, strong, readonly) MWKDataStore *dataStore;
+
+@property (nonatomic, weak) id <SavedArticlesFetcherDelegate> 
fetchFinishedDelegate;
+
+- (instancetype)initAndFetchArticlesForSavedPageList: (MWKSavedPageList 
*)savedPageList
+                                         inDataStore: (MWKDataStore *)dataStore
+                                         withManager: 
(AFHTTPRequestOperationManager *)manager
+                                  thenNotifyDelegate: (id 
<SavedArticlesFetcherDelegate>) delegate;
+
+@end
diff --git a/wikipedia/Networking/Fetchers/SavedArticlesFetcher.m 
b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.m
new file mode 100644
index 0000000..90b647f
--- /dev/null
+++ b/wikipedia/Networking/Fetchers/SavedArticlesFetcher.m
@@ -0,0 +1,106 @@
+
+#import "SavedArticlesFetcher.h"
+#import "ArticleFetcher.h"
+#import "AFHTTPRequestOperationManager.h"
+
+@interface SavedArticlesFetcher()<FetchFinishedDelegate>
+
+@property (nonatomic, strong, readwrite) MWKSavedPageList *savedPageList;
+@property (nonatomic, strong, readwrite) MWKDataStore *dataStore;
+
+@property (nonatomic, strong) NSMutableDictionary *fetchersByArticleTitle;
+@property (nonatomic, strong) NSMutableDictionary *errorsByArticleTitle;
+
+@property (nonatomic, strong) NSMutableArray *fetchedArticles;
+
+@end
+
+@implementation SavedArticlesFetcher
+
+- (instancetype)initAndFetchArticlesForSavedPageList: (MWKSavedPageList 
*)savedPageList
+                                         inDataStore: (MWKDataStore *)dataStore
+                                         withManager: 
(AFHTTPRequestOperationManager *)manager
+                                  thenNotifyDelegate: (id 
<SavedArticlesFetcherDelegate>) delegate{
+    
+    self = [super init];
+    assert(savedPageList != nil);
+    assert(dataStore != nil);
+    assert(manager != nil);
+    assert(delegate != nil);
+    if (self) {
+        self.savedPageList = savedPageList;
+        self.dataStore = dataStore;
+        self.fetchFinishedDelegate = delegate;
+        [self fetchWithManager:manager];
+    }
+    return self;
+    
+}
+
+
+- (void)fetchWithManager:(AFHTTPRequestOperationManager *)manager{
+    
+    [manager.operationQueue cancelAllOperations];
+    
+    self.fetchersByArticleTitle = [NSMutableDictionary dictionary];
+    self.errorsByArticleTitle = [NSMutableDictionary dictionary];
+    self.fetchedArticles = [NSMutableArray array];
+    
+    for (MWKSavedPageEntry* entry in self.savedPageList) {
+        
+        MWKArticle *article = [self.dataStore articleWithTitle:entry.title];
+        article.needsRefresh = YES;
+        
+        self.fetchersByArticleTitle[entry.title] = [[ArticleFetcher alloc] 
initAndFetchSectionsForArticle:article withManager:manager 
thenNotifyDelegate:self];
+        
+    }
+}
+
+- (void)fetchFinished:(id)sender fetchedData:(id)fetchedData 
status:(FetchFinalStatus)status error:(NSError *)error{
+    
+    __block id completedFetcherKey;
+    
+    [self.fetchersByArticleTitle enumerateKeysAndObjectsUsingBlock:^(id key, 
id obj, BOOL *stop) {
+        
+        if([sender isEqual:obj]){
+            completedFetcherKey = key;
+            *stop = YES;
+        }
+    }];
+    
+    if(error){
+        
+        self.errorsByArticleTitle[completedFetcherKey] = error;
+    }
+
+    [self.fetchersByArticleTitle removeObjectForKey:completedFetcherKey];
+    
+    MWKArticle *article = [self.dataStore 
articleWithTitle:completedFetcherKey];
+    
+    [self.fetchedArticles addObject:article];
+
+    [self.fetchFinishedDelegate savedArticlesFetcher:self 
didFetchArticle:article remainingArticles:[self.fetchersByArticleTitle count] 
totalArticles:self.savedPageList.length status:status error:error];
+    
+    if([self.fetchersByArticleTitle count] == 0){
+        [self notifyDelegate];
+    }
+    
+}
+
+
+- (void)notifyDelegate{
+    
+    NSError* reportedError;
+    if([self.errorsByArticleTitle count] > 0)
+        reportedError = [[self.errorsByArticleTitle allValues] firstObject];
+    
+    [self finishWithError: reportedError
+              fetchedData: nil];
+
+    
+}
+
+
+
+
+@end
diff --git a/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.h 
b/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.h
index 85d48af..99a6dca 100644
--- a/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.h
+++ b/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.h
@@ -12,6 +12,7 @@
     NAVBAR_BUTTON_LOGO_W,
     NAVBAR_BUTTON_TOC,
     NAVBAR_BUTTON_MAGNIFY,
+    NAVBAR_BUTTON_RELOAD,
     NAVBAR_BUTTON_BLANK,
     NAVBAR_BUTTON_CANCEL,
     NAVBAR_BUTTON_NEXT,
diff --git a/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.m 
b/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.m
index 731de08..e6c2802 100644
--- a/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.m
+++ b/wikipedia/View Controllers/Navigation/Top/TopMenuViewController.m
@@ -44,6 +44,7 @@
 @property (strong, nonatomic) WikiGlyphButton *buttonArrowLeft;
 @property (strong, nonatomic) WikiGlyphButton *buttonArrowRight;
 @property (strong, nonatomic) WikiGlyphButton *buttonMagnify;
+@property (strong, nonatomic) WikiGlyphButton *buttonReload;
 @property (strong, nonatomic) WikiGlyphButton *buttonBlank;
 @property (strong, nonatomic) WikiGlyphButton *buttonCancel;
 @property (strong, nonatomic) WikiGlyphButton *buttonTrash;
@@ -253,6 +254,7 @@
     self.buttonW =          getWikiGlyphButton(WIKIGLYPH_W,       
MWLocalizedString(@"menu-w-accessibility-label", nil),       
NAVBAR_BUTTON_LOGO_W, size, 2.0f);
     self.buttonTOC =        getWikiGlyphButton(WIKIGLYPH_TOC_COLLAPSED, 
MWLocalizedString(@"menu-toc-accessibility-label", nil),     NAVBAR_BUTTON_TOC, 
size, 2.0f);
     self.buttonMagnify =    getWikiGlyphButton(WIKIGLYPH_MAGNIFY, 
MWLocalizedString(@"menu-search-accessibility-label", nil),  
NAVBAR_BUTTON_MAGNIFY, size, 1.0f);
+    self.buttonReload =    getWikiGlyphButton(WIKIGLYPH_RELOAD, 
MWLocalizedString(@"menu-reload-accessibility-label", nil),  
NAVBAR_BUTTON_RELOAD, size, 1.0f);
     self.buttonBlank =      getWikiGlyphButton(@"",                   @"", 
NAVBAR_BUTTON_BLANK, size, 0.0f);
     self.buttonCancel =     getWikiGlyphButton(@"",                   
MWLocalizedString(@"menu-cancel-accessibility-label", nil),  
NAVBAR_BUTTON_CANCEL, size, 2.0f);
     self.buttonTrash =      getWikiGlyphButton(WIKIGLYPH_TRASH,       
MWLocalizedString(@"menu-trash-accessibility-label", nil),   
NAVBAR_BUTTON_TRASH, size, 2.0f);
@@ -314,6 +316,7 @@
     [self.navBarContainer addSubview:self.buttonW];
     [self.navBarContainer addSubview:self.buttonTOC];
     [self.navBarContainer addSubview:self.buttonMagnify];
+    [self.navBarContainer addSubview:self.buttonReload];
     [self.navBarContainer addSubview:self.buttonBlank];
     [self.navBarContainer addSubview:self.buttonCancel];
     [self.navBarContainer addSubview:self.buttonTrash];
@@ -360,6 +363,7 @@
              @"NAVBAR_BUTTON_LOGO_W": self.buttonW,
              @"NAVBAR_BUTTON_TOC": self.buttonTOC,
              @"NAVBAR_BUTTON_MAGNIFY": self.buttonMagnify,
+             @"NAVBAR_BUTTON_RELOAD": self.buttonReload,
              @"NAVBAR_BUTTON_BLANK": self.buttonBlank,
              @"NAVBAR_BUTTON_CANCEL": self.buttonCancel,
              @"NAVBAR_BUTTON_NEXT": self.buttonNext,
@@ -475,10 +479,13 @@
             @"H:|-(6)-[NAVBAR_BUTTON_ARROW_LEFT(50)][NAVBAR_LABEL]-(56)-|";
             break;
         case NAVBAR_MODE_PAGES_HISTORY:
-        case NAVBAR_MODE_PAGES_SAVED:
             self.navBarSubViewsHorizontalVFLString =
             
@"H:|-(6)-[NAVBAR_BUTTON_X(50)]-(10)-[NAVBAR_LABEL]-(10)-[NAVBAR_BUTTON_TRASH(50@250)]-(6)-|";
             break;
+        case NAVBAR_MODE_PAGES_SAVED:
+            self.navBarSubViewsHorizontalVFLString =
+            
@"H:|-(6)-[NAVBAR_BUTTON_X(50)]-(10)-[NAVBAR_LABEL]-(10)-[NAVBAR_BUTTON_RELOAD(50@250)]-(6)-|";
+            break;
         case NAVBAR_MODE_X_WITH_TEXT_FIELD:
             self.navBarSubViewsHorizontalVFLString =
             @"H:|-(6)-[NAVBAR_BUTTON_X(50)][NAVBAR_TEXT_FIELD]-(16)-|";
diff --git a/wikipedia/View Controllers/SavedPages/SavedPagesViewController.h 
b/wikipedia/View Controllers/SavedPages/SavedPagesViewController.h
index 325b4b8..a1f1ba6 100644
--- a/wikipedia/View Controllers/SavedPages/SavedPagesViewController.h
+++ b/wikipedia/View Controllers/SavedPages/SavedPagesViewController.h
@@ -5,7 +5,7 @@
 #import "TopMenuViewController.h"
 #import "PullToRefreshViewController.h"
 
-@interface SavedPagesViewController : PullToRefreshViewController 
<UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate>
+@interface SavedPagesViewController : UIViewController <UITableViewDataSource, 
UITableViewDelegate, UIAlertViewDelegate>
 
 @property (nonatomic) NavBarMode navBarMode;
 @property (weak, nonatomic) IBOutlet UIView *emptyOverlay;
diff --git a/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m 
b/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
index f864400..11209b3 100644
--- a/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
+++ b/wikipedia/View Controllers/SavedPages/SavedPagesViewController.m
@@ -18,13 +18,22 @@
 #import "SavedPagesFunnel.h"
 #import "NSObject+ConstraintsScale.h"
 #import "PaddedLabel.h"
+#import "QueuesSingleton.h"
+#import "SavedArticlesFetcher.h"
+#import "WMFBorderButton.h"
+#import "WMFProgressLineView.h"
+#import <Masonry/Masonry.h>
 
 #define SAVED_PAGES_TITLE_TEXT_COLOR [UIColor colorWithWhite:0.0f alpha:0.7f]
 #define SAVED_PAGES_TEXT_COLOR [UIColor colorWithWhite:0.0f alpha:1.0f]
 #define SAVED_PAGES_LANGUAGE_COLOR [UIColor colorWithWhite:0.0f alpha:0.4f]
 #define SAVED_PAGES_RESULT_HEIGHT (116.0 * MENUS_SCALE_MULTIPLIER)
 
-@interface SavedPagesViewController ()
+static NSString* const WMFSavedPagesDidShowCancelRefreshAlert = 
@"WMFSavedPagesDidShowCancelRefreshAlert";
+
+static SavedArticlesFetcher* _sharedFetcher = nil;
+
+@interface SavedPagesViewController ()<SavedArticlesFetcherDelegate>
 {
     MWKSavedPageList *savedPageList;
     MWKUserDataStore *userDataStore;
@@ -40,9 +49,34 @@
 
 @property (strong, nonatomic) IBOutlet UIView *emptyContainerView;
 
+@property (strong, nonatomic) WMFProgressLineView *progressView;
+@property (strong, nonatomic) WMFBorderButton *cancelButton;
+
 @end
 
 @implementation SavedPagesViewController
+
+#pragma mark - Shared Access
+
++ (SavedArticlesFetcher*)sharedFetcher{
+    
+    return _sharedFetcher;
+}
+
++ (void)setSharedFetcher:(SavedArticlesFetcher*)fetcher{
+    
+    _sharedFetcher = fetcher;
+}
+
+#pragma mark - Lifecycle
+
+- (void)dealloc{
+    
+    [[self class] sharedFetcher].fetchFinishedDelegate = nil;
+}
+
+#pragma mark - NavBar
+
 
 -(NavBarMode)navBarMode
 {
@@ -53,6 +87,37 @@
 {
     return MWLocalizedString(@"saved-pages-title", nil);
 }
+
+- (MenuButton*)reloadButton{
+    
+    return (MenuButton *)[self.topMenuViewController 
getNavBarItem:NAVBAR_BUTTON_RELOAD];
+}
+
+- (WMFBorderButton*)cancelButton{
+    
+    if(!_cancelButton){
+        
+        WMFBorderButton* button = [WMFBorderButton standardBorderButton];
+        [button setTitle:MWLocalizedString(@"saved-pages-clear-cancel", nil) 
forState:UIControlStateNormal];
+        [button addTarget:self action:@selector(cancelRefresh) 
forControlEvents:UIControlEventTouchUpInside];
+
+        _cancelButton = button;
+    }
+    
+    return _cancelButton;
+}
+
+- (WMFProgressLineView*)progressView{
+    
+    if(!_progressView){
+        
+        WMFProgressLineView* progress = [[WMFProgressLineView alloc] 
initWithFrame:CGRectZero];
+        _progressView = progress;
+    }
+    
+    return _progressView;
+}
+
 
 #pragma mark - Memory
 
@@ -72,12 +137,11 @@
 
     switch (tappedItem.tag) {
         case NAVBAR_BUTTON_X:
-        case NAVBAR_LABEL:
             [self popModal];
-
+        case NAVBAR_LABEL:
             break;
-        case NAVBAR_BUTTON_TRASH:
-            [self showDeleteAllDialog];
+        case NAVBAR_BUTTON_RELOAD:
+            [self startRefresh];
             break;
         default:
             break;
@@ -89,7 +153,7 @@
     return NO;
 }
 
-#pragma mark - View lifecycle
+#pragma mark - UIViewController
 
 -(void)viewWillDisappear:(BOOL)animated
 {
@@ -110,6 +174,17 @@
                                              selector: 
@selector(navItemTappedNotification:)
                                                  name: @"NavItemTapped"
                                                object: nil];
+    
+    
+    SavedArticlesFetcher* fetcher = [[self class] sharedFetcher];
+    
+    if(fetcher){
+        
+        fetcher.fetchFinishedDelegate = self;
+        [self showCancelButton];
+        [self showProgressView];
+    }
+
 }
 
 - (void)viewDidLoad
@@ -122,13 +197,9 @@
     self.funnel = [[SavedPagesFunnel alloc] init];
 
     self.navigationItem.hidesBackButton = YES;
-    
-    // Uncomment the following line to preserve selection between 
presentations.
-    // self.clearsSelectionOnViewWillAppear = NO;
-    
-    // Uncomment the following line to display an Edit button in the 
navigation bar for this view controller.
-    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
-    
+        
+    self.tableView.rowHeight = SAVED_PAGES_RESULT_HEIGHT;
+
     UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10.0 * 
MENUS_SCALE_MULTIPLIER, 5.0 * MENUS_SCALE_MULTIPLIER)];
     self.tableView.tableHeaderView = headerView;
     
@@ -146,10 +217,11 @@
     
     self.emptyTitle.font = [UIFont boldSystemFontOfSize:17.0 * 
MENUS_SCALE_MULTIPLIER];
     self.emptyDescription.font = [UIFont systemFontOfSize:14.0 * 
MENUS_SCALE_MULTIPLIER];
+
 }
 
 
-#pragma mark - Table view data source
+#pragma mark - UITableViewDataSource
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 {
@@ -221,24 +293,6 @@
     return cell;
 }
 
-- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    MWKSavedPageEntry *savedEntry = [savedPageList entryAtIndex:indexPath.row];
-    
-    [NAV loadArticleWithTitle: savedEntry.title
-                     animated: YES
-              discoveryMethod: MWK_DISCOVERY_METHOD_SAVED
-                   popToWebVC: NO];
-
-    [self popModalToRoot];
-}
-
-- (CGFloat)tableView:(UITableView *)tableView 
heightForRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    return SAVED_PAGES_RESULT_HEIGHT;
-}
-
-#pragma mark - Delete
-
 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath 
*)indexPath {
     return YES;
 }
@@ -250,13 +304,108 @@
     }
 }
 
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    MWKSavedPageEntry *savedEntry = [savedPageList entryAtIndex:indexPath.row];
+    
+    [NAV loadArticleWithTitle: savedEntry.title
+                     animated: YES
+              discoveryMethod: MWK_DISCOVERY_METHOD_SAVED
+                   popToWebVC: NO];
+
+    [self popModalToRoot];
+}
+
+#pragma mark - UI Updates
+
+- (void)showCancelButton{
+    
+    self.cancelButton.alpha = 1.0;
+    [self.topMenuViewController.view addSubview:self.cancelButton];
+
+    MenuButton *reloadButton = [self reloadButton];
+
+    [self.cancelButton mas_remakeConstraints:^(MASConstraintMaker *make) {
+        
+        make.right.equalTo(reloadButton.mas_right);
+        make.centerY.equalTo(reloadButton.mas_centerY);
+    }];
+}
+
+- (void)hideCancelButton{
+    
+    self.cancelButton.alpha = 0.0;
+}
+
+- (void)showRefreshButton{
+    
+    MenuButton *reloadButton = [self reloadButton];
+    reloadButton.alpha = 1.0;
+}
+
+- (void)hideRefreshButton{
+    
+    MenuButton *reloadButton = [self reloadButton];
+    reloadButton.clipsToBounds = NO;
+    reloadButton.alpha = 0.0;
+}
+
+- (void)showProgressView{
+    
+    self.progressView.alpha = 1.0;
+    [self.topMenuViewController.view addSubview:self.progressView];
+    
+    [self.progressView mas_remakeConstraints:^(MASConstraintMaker *make) {
+        
+        make.top.equalTo(self.topMenuViewController.view.mas_bottom);
+        make.left.equalTo(self.topMenuViewController.view.mas_left);
+        make.right.equalTo(self.topMenuViewController.view.mas_right);
+        make.height.equalTo(@2.0);
+    }];
+}
+
+- (void)hideProgressView{
+    
+    self.progressView.alpha = 0.0;
+}
+
+-(void)setEmptyOverlayAndTrashIconVisibility
+{
+    BOOL savedPageFound = (savedPageList.length > 0);
+    
+    self.emptyOverlay.hidden = savedPageFound;
+    
+    MenuButton *reloadButton = [self reloadButton];
+    reloadButton.alpha = savedPageFound ? 1.0 : 0.0;
+}
+
+- (void)showCancelRefreshAlertIfFirstTime{
+    
+    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+    
+    BOOL didShowAlert = [defaults 
boolForKey:WMFSavedPagesDidShowCancelRefreshAlert];
+    
+    if(!didShowAlert){
+        
+        UIAlertView *alert = [[UIAlertView alloc] 
initWithTitle:MWLocalizedString(@"saved-pages-refresh-cancel-alert-title", nil) 
message:MWLocalizedString(@"saved-pages-refresh-cancel-alert-message", nil) 
delegate:nil 
cancelButtonTitle:MWLocalizedString(@"saved-pages-refresh-cancel-alert-button", 
nil) otherButtonTitles:nil];
+        
+        [alert show];
+        
+        [defaults setBool:YES forKey:WMFSavedPagesDidShowCancelRefreshAlert];
+    }
+}
+
+#pragma mrk - Delete
+
 -(void)deleteSavedPageForIndexPath:(NSIndexPath *)indexPath
 {
     MWKSavedPageEntry *savedEntry = [savedPageList entryAtIndex:indexPath.row];
     if (savedEntry) {
         
         [self.tableView beginUpdates];
-
+        
         [self.tableView deleteRowsAtIndexPaths:@[indexPath] 
withRowAnimation:UITableViewRowAnimationFade];
         
         // Delete the saved record.
@@ -264,12 +413,12 @@
         [userDataStore save];
         
         [self.tableView endUpdates];
-
+        
         [self setEmptyOverlayAndTrashIconVisibility];
         
         [self.funnel logDelete];
     }
-
+    
     // Remove any orphaned images.
     DataHousekeeping *dataHouseKeeping = [[DataHousekeeping alloc] init];
     [dataHouseKeeping performHouseKeeping];
@@ -281,7 +430,7 @@
 {
     [savedPageList removeAllEntries];
     [userDataStore save];
-
+    
     // Remove any orphaned images.
     DataHousekeeping *dataHouseKeeping = [[DataHousekeeping alloc] init];
     [dataHouseKeeping performHouseKeeping];
@@ -291,16 +440,6 @@
     [self setEmptyOverlayAndTrashIconVisibility];
     
     [NAV loadTodaysArticleIfNoCoreDataForCurrentArticle];
-}
-
--(void)setEmptyOverlayAndTrashIconVisibility
-{
-    BOOL savedPageFound = (savedPageList.length > 0);
-    
-    self.emptyOverlay.hidden = savedPageFound;
-
-    MenuButton *trashButton = (MenuButton *)[self.topMenuViewController 
getNavBarItem:NAVBAR_BUTTON_TRASH];
-    trashButton.alpha = savedPageFound ? 1.0 : 0.0;
 }
 
 - (void)alertView:(UIAlertView *)alertView 
clickedButtonAtIndex:(NSInteger)buttonIndex
@@ -321,11 +460,93 @@
     [dialog show];
 }
 
-#pragma mark - Pull to refresh
 
-- (UIScrollView *)refreshScrollView
-{
-    return self.tableView;
+#pragma mark - Refresh
+
+- (void)startRefresh{
+    
+    [[QueuesSingleton sharedInstance].savedPagesFetchManager.operationQueue 
cancelAllOperations];
+    
+    SavedArticlesFetcher* fetcher = [[SavedArticlesFetcher alloc] 
initAndFetchArticlesForSavedPageList:savedPageList 
inDataStore:userDataStore.dataStore withManager:[QueuesSingleton 
sharedInstance].savedPagesFetchManager thenNotifyDelegate:self];
+    
+    [[self class] setSharedFetcher:fetcher];
+
+    self.progressView.progress = 0.0;
+
+    [UIView animateWithDuration:0.25 animations:^{
+        
+        [self hideRefreshButton];
+        [self showProgressView];
+        
+    } completion:^(BOOL finished) {
+        
+        [UIView animateWithDuration:0.25 animations:^{
+            
+            [self showCancelButton];
+        }];
+    }];
 }
 
+- (void)resumeRefresh{
+    
+    self.progressView.progress = 0.0;
+    [self showProgressView];
+    [self showCancelButton];
+}
+
+- (void)finishRefresh{
+    
+    [[self class] sharedFetcher].fetchFinishedDelegate = nil;
+    [[self class] setSharedFetcher:nil];
+
+    [UIView animateWithDuration:0.25 animations:^{
+        
+        [self hideProgressView];
+        [self hideCancelButton];
+        
+    } completion:^(BOOL finished) {
+        
+        [UIView animateWithDuration:0.25 animations:^{
+            
+            [self showRefreshButton];
+        }];
+        
+        self.progressView.progress = 0.0;
+    }];
+}
+
+- (void)cancelRefresh{
+    
+    [[QueuesSingleton sharedInstance].savedPagesFetchManager.operationQueue 
cancelAllOperations];
+    
+    [self finishRefresh];
+    
+    [self showCancelRefreshAlertIfFirstTime];
+}
+
+#pragma mark - SavedArticlesFetcherDelegate
+
+- (void)savedArticlesFetcher:(SavedArticlesFetcher *)savedArticlesFetcher 
didFetchArticle:(MWKArticle *)article remainingArticles:(NSInteger)remaining 
totalArticles:(NSInteger)total status:(FetchFinalStatus)status error:(NSError 
*)error{
+    
+    CGFloat progress = 1.0-(CGFloat)((CGFloat)remaining/(CGFloat)total);
+    [self.progressView setProgress:progress animated:YES];
+    
+}
+
+- (void)fetchFinished:(id)sender fetchedData:(id)fetchedData 
status:(FetchFinalStatus)status error:(NSError *)error{
+    
+    __weak __typeof(self)weakSelf = self;
+    
+    [self.progressView setProgress:1.0 animated:YES completion:^{
+        
+        __strong __typeof(weakSelf)strongSelf = weakSelf;
+
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * 
NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            [strongSelf finishRefresh];
+        });
+        
+    }];
+}
+
+
 @end
diff --git a/wikipedia/en.lproj/Localizable.strings 
b/wikipedia/en.lproj/Localizable.strings
index 65e3048..1314d30 100644
--- a/wikipedia/en.lproj/Localizable.strings
+++ b/wikipedia/en.lproj/Localizable.strings
@@ -130,7 +130,10 @@
 "saved-pages-clear-confirmation-heading" = "Delete all saved items?";
 "saved-pages-clear-confirmation-sub-heading" = "This action cannot be undone!";
 "saved-pages-clear-cancel" = "Cancel";
-"saved-pages-clear-delete-all" = "Delete All";
+"saved-pages-refresh-cancel-alert-title" = "Update cancelled";
+"saved-pages-refresh-cancel-alert-message" = "Wikipedia articles are updated 
all the time. Tap refresh to get the latest versions of all the articles.";
+"saved-pages-refresh-cancel-alert-button" = "Got it";
+
 
 "page-history-title" = "Article history";
 "page-history-downloading" = "Loading article history...";
@@ -250,6 +253,7 @@
 "menu-w-accessibility-label" = "Menu";
 "menu-toc-accessibility-label" = "Contents";
 "menu-search-accessibility-label" = "Search";
+"menu-reload-accessibility-label" = "Reload";
 "menu-cancel-accessibility-label" = "Cancel";
 "menu-share-accessibility-label" = "Share";
 "menu-more-accessibility-label" = "More settings";
diff --git a/wikipedia/qqq.lproj/Localizable.strings 
b/wikipedia/qqq.lproj/Localizable.strings
index 62d1c78..191f2bf 100644
--- a/wikipedia/qqq.lproj/Localizable.strings
+++ b/wikipedia/qqq.lproj/Localizable.strings
@@ -126,7 +126,9 @@
 "saved-pages-clear-confirmation-heading" = "Heading text of delete all 
confirmation dialog";
 "saved-pages-clear-confirmation-sub-heading" = "Sub-heading text of delete all 
confirmation dialog";
 "saved-pages-clear-cancel" = "Button text for cancelling delete all 
action\n{{Identical|Cancel}}";
-"saved-pages-clear-delete-all" = "Button text for confirming delete all 
action\n{{Identical|Delete all}}";
+"saved-pages-refresh-cancel-alert-title" = "Title of alert shown when a user 
first cancels a refresh of saved pages";
+"saved-pages-refresh-cancel-alert-message" = "Message of alert shown when a 
user first cancels a refresh of saved pages";
+"saved-pages-refresh-cancel-alert-button" = "Butto confirmation text of alert 
shown when a user first cancels a refresh of saved pages";
 "page-history-title" = "Header text for Page History 
interface.\n{{Identical|Page history}}";
 "page-history-downloading" = "Alert text shown when obtaining revision history 
of an article";
 "navbar-title-mode-edit-wikitext" = "Header text shown when wikitext is being 
edited.\n{{Identical|Edit}}";
@@ -225,6 +227,7 @@
 "menu-w-accessibility-label" = "Accessible label text for toolbar 'W' menu 
icon button\n{{Identical|Menu}}";
 "menu-toc-accessibility-label" = "Accessible label text for toolbar table of 
contents icon button\n{{Identical|Content}}";
 "menu-search-accessibility-label" = "Accessible label text for toolbar search 
icon button\n{{Identical|Search}}";
+"menu-reload-accessibility-label" = "Accessible label text for toolbar reload 
icon button";
 "menu-cancel-accessibility-label" = "Accessible label text for toolbar cancel 
button\n{{Identical|Cancel}}";
 "menu-share-accessibility-label" = "Accessible label text for toolbar share 
button\n{{Identical|Share}}";
 "menu-more-accessibility-label" = "Accessible label for more/... button in 'W' 
menu";

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I0d03cec85081738211bb120593cd2777ba8518d8
Gerrit-PatchSet: 1
Gerrit-Project: apps/ios/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Fjalapeno <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to