OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import <MobileCoreServices/MobileCoreServices.h> |
| 6 |
| 7 #import "ios/chrome/share_extension/share_view_controller.h" |
| 8 |
| 9 #import "base/ios/block_types.h" |
| 10 #import "base/mac/foundation_util.h" |
| 11 #import "ios/chrome/common/app_group/app_group_constants.h" |
| 12 #import "ios/chrome/share_extension/share_extension_view.h" |
| 13 #import "ios/chrome/share_extension/ui_util.h" |
| 14 |
| 15 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 16 #error "This file requires ARC support." |
| 17 #endif |
| 18 |
| 19 // Type for completion handler to fetch the components of the share items. |
| 20 // |idResponse| type depends on the element beeing fetched. |
| 21 using ItemBlock = void (^)(id idResponse, NSError* error); |
| 22 |
| 23 namespace { |
| 24 |
| 25 // Minimum size around the widget |
| 26 const CGFloat kShareExtensionMargin = 15; |
| 27 const CGFloat kShareExtensionMaxWidth = 390; |
| 28 // Clip the last separator out of the table view. |
| 29 const CGFloat kScreenShotWidth = 100; |
| 30 const CGFloat kScreenShotHeight = 100; |
| 31 const CGFloat kAnimationDuration = 0.3; |
| 32 const CGFloat kMediumAlpha = 0.5; |
| 33 |
| 34 } // namespace |
| 35 |
| 36 @interface ShareViewController ()<ShareExtensionViewActionTarget> { |
| 37 // This constraint the center of the widget to be vertically in the center |
| 38 // of the the screen. It has to be modified for the dismissal animation. |
| 39 NSLayoutConstraint* _centerYConstraint; |
| 40 |
| 41 NSURL* _shareURL; |
| 42 NSString* _shareTitle; |
| 43 NSExtensionItem* _shareItem; |
| 44 } |
| 45 |
| 46 @property(nonatomic, weak) UIView* maskView; |
| 47 @property(nonatomic, weak) ShareExtensionView* shareView; |
| 48 @property(nonatomic, assign) app_group::ShareExtensionItemType itemType; |
| 49 |
| 50 // Creates a files in |app_group::ShareExtensionItemsFolder()| containing a |
| 51 // serialized NSDictionary. |
| 52 // If |cancel| is true, |actionType| is ignored. |
| 53 - (void)queueActionItemURL:(NSURL*)URL |
| 54 title:(NSString*)title |
| 55 action:(app_group::ShareExtensionItemType)actionType |
| 56 cancel:(BOOL)cancel |
| 57 completion:(ProceduralBlock)completion; |
| 58 |
| 59 // Loads all the shared elements from the extension context and update the UI. |
| 60 - (void)loadElementsFromContext; |
| 61 |
| 62 // Performs a fade in animation for the whole widget. |
| 63 - (void)fadeIn; |
| 64 |
| 65 // Sets constaints to the widget so that margin are at least |
| 66 // kShareExtensionMargin points and widget width is closest up to |
| 67 // kShareExtensionMaxWidth points. |
| 68 - (void)constrainWidgetWidth; |
| 69 |
| 70 @end |
| 71 |
| 72 @implementation ShareViewController |
| 73 |
| 74 @synthesize maskView = _maskView; |
| 75 @synthesize shareView = _shareView; |
| 76 @synthesize itemType = _itemType; |
| 77 |
| 78 - (void)viewDidLoad { |
| 79 [super viewDidLoad]; |
| 80 |
| 81 // This view shadows the screen under the share extension. |
| 82 UIView* maskView = [[UIView alloc] initWithFrame:CGRectZero]; |
| 83 [self setMaskView:maskView]; |
| 84 [self.maskView |
| 85 setBackgroundColor:[UIColor colorWithWhite:0 alpha:kMediumAlpha]]; |
| 86 [self.view addSubview:self.maskView]; |
| 87 ui_util::ConstrainAllSidesOfViewToView(self.view, self.maskView); |
| 88 |
| 89 // This view is the main view of the share extension. |
| 90 ShareExtensionView* shareView = |
| 91 [[ShareExtensionView alloc] initWithActionTarget:self]; |
| 92 [self setShareView:shareView]; |
| 93 [self.view addSubview:self.shareView]; |
| 94 |
| 95 [self constrainWidgetWidth]; |
| 96 |
| 97 // Center the widget in the screen. |
| 98 _centerYConstraint = [[shareView centerYAnchor] |
| 99 constraintEqualToAnchor:[self.view centerYAnchor]]; |
| 100 [_centerYConstraint setActive:YES]; |
| 101 [[[shareView centerXAnchor] constraintEqualToAnchor:[self.view centerXAnchor]] |
| 102 setActive:YES]; |
| 103 |
| 104 [self.maskView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 105 [self.shareView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 106 |
| 107 [self loadElementsFromContext]; |
| 108 [self fadeIn]; |
| 109 } |
| 110 |
| 111 - (void)constrainWidgetWidth { |
| 112 // Setting the constraints. |
| 113 NSDictionary* views = @{ @"share" : self.shareView }; |
| 114 |
| 115 NSDictionary* metrics = @{ |
| 116 @"margin" : @(kShareExtensionMargin), |
| 117 @"maxWidth" : @(kShareExtensionMaxWidth), |
| 118 }; |
| 119 |
| 120 NSArray* constraints = @[ |
| 121 // Sets the margin around the share extension. |
| 122 @"H:|-(>=margin)-[share(<=maxWidth)]-(>=margin)-|", |
| 123 // If the screen is too small, reduce width of widget. |
| 124 @"H:[share(==maxWidth@900)]", |
| 125 ]; |
| 126 |
| 127 for (NSString* constraint : constraints) { |
| 128 [NSLayoutConstraint |
| 129 activateConstraints:[NSLayoutConstraint |
| 130 constraintsWithVisualFormat:constraint |
| 131 options:0 |
| 132 metrics:metrics |
| 133 views:views]]; |
| 134 } |
| 135 |
| 136 // |self.shareView| must be as large as possible and in the center of the |
| 137 // screen. |
| 138 [self.shareView |
| 139 setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh |
| 140 forAxis:UILayoutConstraintAxisHorizontal]; |
| 141 } |
| 142 |
| 143 - (void)fadeIn { |
| 144 // Fade in animation. |
| 145 [self.maskView setAlpha:0]; |
| 146 [self.shareView setAlpha:0]; |
| 147 [UIView animateWithDuration:kAnimationDuration |
| 148 animations:^{ |
| 149 [self.maskView setAlpha:kMediumAlpha]; |
| 150 [self.shareView setAlpha:1]; |
| 151 }]; |
| 152 } |
| 153 |
| 154 - (void)loadElementsFromContext { |
| 155 NSString* typeURL = static_cast<NSString*>(kUTTypeURL); |
| 156 for (NSExtensionItem* item in self.extensionContext.inputItems) { |
| 157 for (NSItemProvider* itemProvider in item.attachments) { |
| 158 if ([itemProvider hasItemConformingToTypeIdentifier:typeURL]) { |
| 159 ItemBlock URLCompletion = ^(id idURL, NSError* error) { |
| 160 NSURL* URL = static_cast<NSURL*>(idURL); |
| 161 dispatch_async(dispatch_get_main_queue(), ^{ |
| 162 _shareItem = [item copy]; |
| 163 _shareURL = [URL copy]; |
| 164 _shareTitle = [[[item attributedContentText] string] copy]; |
| 165 if ([_shareTitle length] == 0) { |
| 166 _shareTitle = [URL host]; |
| 167 } |
| 168 [self.shareView setURL:URL]; |
| 169 [self.shareView setTitle:_shareTitle]; |
| 170 }); |
| 171 |
| 172 }; |
| 173 [itemProvider loadItemForTypeIdentifier:typeURL |
| 174 options:nil |
| 175 completionHandler:URLCompletion]; |
| 176 NSDictionary* imageOptions = @{ |
| 177 NSItemProviderPreferredImageSizeKey : [NSValue |
| 178 valueWithCGSize:CGSizeMake(kScreenShotWidth, kScreenShotHeight)] |
| 179 }; |
| 180 ItemBlock ImageCompletion = ^(id item, NSError* error) { |
| 181 |
| 182 UIImage* image = static_cast<UIImage*>(item); |
| 183 if (image) { |
| 184 dispatch_async(dispatch_get_main_queue(), ^{ |
| 185 [self.shareView setScreenshot:image]; |
| 186 }); |
| 187 } |
| 188 |
| 189 }; |
| 190 [itemProvider loadPreviewImageWithOptions:imageOptions |
| 191 completionHandler:ImageCompletion]; |
| 192 } |
| 193 } |
| 194 } |
| 195 } |
| 196 |
| 197 - (void)dismissAndReturnItem:(NSExtensionItem*)item { |
| 198 // Set the Y center constraints so the whole extension slides up out of the |
| 199 // screen. Constant is relative to the center of the screen. |
| 200 [_centerYConstraint setConstant:-(self.view.frame.size.height + |
| 201 self.shareView.frame.size.height) / |
| 202 2]; |
| 203 [UIView animateWithDuration:kAnimationDuration |
| 204 animations:^{ |
| 205 [self.maskView setAlpha:0]; |
| 206 [self.view layoutIfNeeded]; |
| 207 } |
| 208 completion:^(BOOL finished) { |
| 209 NSArray* returnItem = item ? @[ item ] : @[]; |
| 210 [self.extensionContext completeRequestReturningItems:returnItem |
| 211 completionHandler:nil]; |
| 212 }]; |
| 213 } |
| 214 |
| 215 - (void)queueActionItemURL:(NSURL*)URL |
| 216 title:(NSString*)title |
| 217 action:(app_group::ShareExtensionItemType)actionType |
| 218 cancel:(BOOL)cancel |
| 219 completion:(ProceduralBlock)completion { |
| 220 NSURL* readingListURL = app_group::ShareExtensionItemsFolder(); |
| 221 if (![[NSFileManager defaultManager] |
| 222 fileExistsAtPath:[readingListURL path]]) { |
| 223 [[NSFileManager defaultManager] createDirectoryAtPath:[readingListURL path] |
| 224 withIntermediateDirectories:YES |
| 225 attributes:nil |
| 226 error:nil]; |
| 227 } |
| 228 NSDate* date = [NSDate date]; |
| 229 NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init]; |
| 230 // This format sorts files by alphabetical order. |
| 231 [dateFormatter setDateFormat:@"yyyy-MM-dd-HH-mm-ss.SSSSSS"]; |
| 232 NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; |
| 233 [dateFormatter setTimeZone:timeZone]; |
| 234 NSString* dateString = [dateFormatter stringFromDate:date]; |
| 235 NSURL* fileURL = |
| 236 [readingListURL URLByAppendingPathComponent:dateString isDirectory:NO]; |
| 237 |
| 238 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; |
| 239 if (URL) |
| 240 [dict setObject:URL forKey:app_group::kShareItemURL]; |
| 241 if (title) |
| 242 [dict setObject:title forKey:app_group::kShareItemTitle]; |
| 243 [dict setObject:date forKey:app_group::kShareItemDate]; |
| 244 |
| 245 if (!cancel) { |
| 246 NSNumber* entryType = [NSNumber numberWithInteger:actionType]; |
| 247 [dict setObject:entryType forKey:app_group::kShareItemType]; |
| 248 } |
| 249 |
| 250 [dict setValue:[NSNumber numberWithBool:cancel] |
| 251 forKey:app_group::kShareItemCancel]; |
| 252 NSData* data = [NSKeyedArchiver archivedDataWithRootObject:dict]; |
| 253 [[NSFileManager defaultManager] createFileAtPath:[fileURL path] |
| 254 contents:data |
| 255 attributes:nil]; |
| 256 if (completion) { |
| 257 completion(); |
| 258 } |
| 259 } |
| 260 |
| 261 #pragma mark - ShareExtensionViewActionTarget |
| 262 |
| 263 - (void)shareExtensionViewDidSelectCancel:(id)sender { |
| 264 [self queueActionItemURL:nil |
| 265 title:nil |
| 266 action:app_group::READING_LIST_ITEM // Ignored |
| 267 cancel:YES |
| 268 completion:^{ |
| 269 [self dismissAndReturnItem:nil]; |
| 270 }]; |
| 271 } |
| 272 |
| 273 - (void)shareExtensionViewDidSelectAddToReadingList:(id)sender { |
| 274 [self queueActionItemURL:_shareURL |
| 275 title:_shareTitle |
| 276 action:app_group::READING_LIST_ITEM |
| 277 cancel:NO |
| 278 completion:^{ |
| 279 [self dismissAndReturnItem:_shareItem]; |
| 280 }]; |
| 281 } |
| 282 |
| 283 - (void)shareExtensionViewDidSelectAddToBookmarks:(id)sender { |
| 284 [self queueActionItemURL:_shareURL |
| 285 title:_shareTitle |
| 286 action:app_group::BOOKMARK_ITEM |
| 287 cancel:NO |
| 288 completion:^{ |
| 289 [self dismissAndReturnItem:_shareItem]; |
| 290 }]; |
| 291 } |
| 292 |
| 293 - (void)shareExtensionView:(id)sender |
| 294 typeChanged:(app_group::ShareExtensionItemType)type { |
| 295 [self setItemType:type]; |
| 296 } |
| 297 |
| 298 @end |
OLD | NEW |