| Index: ios/chrome/share_extension/share_view_controller.mm
|
| diff --git a/ios/chrome/share_extension/share_view_controller.mm b/ios/chrome/share_extension/share_view_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cfd96dd90ff60adbffcf114b6b902b6e09a8191b
|
| --- /dev/null
|
| +++ b/ios/chrome/share_extension/share_view_controller.mm
|
| @@ -0,0 +1,298 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import <MobileCoreServices/MobileCoreServices.h>
|
| +
|
| +#import "ios/chrome/share_extension/share_view_controller.h"
|
| +
|
| +#import "base/ios/block_types.h"
|
| +#import "base/mac/foundation_util.h"
|
| +#import "ios/chrome/common/app_group/app_group_constants.h"
|
| +#import "ios/chrome/share_extension/share_extension_view.h"
|
| +#import "ios/chrome/share_extension/ui_util.h"
|
| +
|
| +#if !defined(__has_feature) || !__has_feature(objc_arc)
|
| +#error "This file requires ARC support."
|
| +#endif
|
| +
|
| +// Type for completion handler to fetch the components of the share items.
|
| +// |idResponse| type depends on the element beeing fetched.
|
| +using ItemBlock = void (^)(id idResponse, NSError* error);
|
| +
|
| +namespace {
|
| +
|
| +// Minimum size around the widget
|
| +const CGFloat kShareExtensionMargin = 15;
|
| +const CGFloat kShareExtensionMaxWidth = 390;
|
| +// Clip the last separator out of the table view.
|
| +const CGFloat kScreenShotWidth = 100;
|
| +const CGFloat kScreenShotHeight = 100;
|
| +const CGFloat kAnimationDuration = 0.3;
|
| +const CGFloat kMediumAlpha = 0.5;
|
| +
|
| +} // namespace
|
| +
|
| +@interface ShareViewController ()<ShareExtensionViewActionTarget> {
|
| + // This constraint the center of the widget to be vertically in the center
|
| + // of the the screen. It has to be modified for the dismissal animation.
|
| + NSLayoutConstraint* _centerYConstraint;
|
| +
|
| + NSURL* _shareURL;
|
| + NSString* _shareTitle;
|
| + NSExtensionItem* _shareItem;
|
| +}
|
| +
|
| +@property(nonatomic, weak) UIView* maskView;
|
| +@property(nonatomic, weak) ShareExtensionView* shareView;
|
| +@property(nonatomic, assign) app_group::ShareExtensionItemType itemType;
|
| +
|
| +// Creates a files in |app_group::ShareExtensionItemsFolder()| containing a
|
| +// serialized NSDictionary.
|
| +// If |cancel| is true, |actionType| is ignored.
|
| +- (void)queueActionItemURL:(NSURL*)URL
|
| + title:(NSString*)title
|
| + action:(app_group::ShareExtensionItemType)actionType
|
| + cancel:(BOOL)cancel
|
| + completion:(ProceduralBlock)completion;
|
| +
|
| +// Loads all the shared elements from the extension context and update the UI.
|
| +- (void)loadElementsFromContext;
|
| +
|
| +// Performs a fade in animation for the whole widget.
|
| +- (void)fadeIn;
|
| +
|
| +// Sets constaints to the widget so that margin are at least
|
| +// kShareExtensionMargin points and widget width is closest up to
|
| +// kShareExtensionMaxWidth points.
|
| +- (void)constrainWidgetWidth;
|
| +
|
| +@end
|
| +
|
| +@implementation ShareViewController
|
| +
|
| +@synthesize maskView = _maskView;
|
| +@synthesize shareView = _shareView;
|
| +@synthesize itemType = _itemType;
|
| +
|
| +- (void)viewDidLoad {
|
| + [super viewDidLoad];
|
| +
|
| + // This view shadows the screen under the share extension.
|
| + UIView* maskView = [[UIView alloc] initWithFrame:CGRectZero];
|
| + [self setMaskView:maskView];
|
| + [self.maskView
|
| + setBackgroundColor:[UIColor colorWithWhite:0 alpha:kMediumAlpha]];
|
| + [self.view addSubview:self.maskView];
|
| + ui_util::ConstrainAllSidesOfViewToView(self.view, self.maskView);
|
| +
|
| + // This view is the main view of the share extension.
|
| + ShareExtensionView* shareView =
|
| + [[ShareExtensionView alloc] initWithActionTarget:self];
|
| + [self setShareView:shareView];
|
| + [self.view addSubview:self.shareView];
|
| +
|
| + [self constrainWidgetWidth];
|
| +
|
| + // Center the widget in the screen.
|
| + _centerYConstraint = [[shareView centerYAnchor]
|
| + constraintEqualToAnchor:[self.view centerYAnchor]];
|
| + [_centerYConstraint setActive:YES];
|
| + [[[shareView centerXAnchor] constraintEqualToAnchor:[self.view centerXAnchor]]
|
| + setActive:YES];
|
| +
|
| + [self.maskView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| + [self.shareView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| +
|
| + [self loadElementsFromContext];
|
| + [self fadeIn];
|
| +}
|
| +
|
| +- (void)constrainWidgetWidth {
|
| + // Setting the constraints.
|
| + NSDictionary* views = @{ @"share" : self.shareView };
|
| +
|
| + NSDictionary* metrics = @{
|
| + @"margin" : @(kShareExtensionMargin),
|
| + @"maxWidth" : @(kShareExtensionMaxWidth),
|
| + };
|
| +
|
| + NSArray* constraints = @[
|
| + // Sets the margin around the share extension.
|
| + @"H:|-(>=margin)-[share(<=maxWidth)]-(>=margin)-|",
|
| + // If the screen is too small, reduce width of widget.
|
| + @"H:[share(==maxWidth@900)]",
|
| + ];
|
| +
|
| + for (NSString* constraint : constraints) {
|
| + [NSLayoutConstraint
|
| + activateConstraints:[NSLayoutConstraint
|
| + constraintsWithVisualFormat:constraint
|
| + options:0
|
| + metrics:metrics
|
| + views:views]];
|
| + }
|
| +
|
| + // |self.shareView| must be as large as possible and in the center of the
|
| + // screen.
|
| + [self.shareView
|
| + setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh
|
| + forAxis:UILayoutConstraintAxisHorizontal];
|
| +}
|
| +
|
| +- (void)fadeIn {
|
| + // Fade in animation.
|
| + [self.maskView setAlpha:0];
|
| + [self.shareView setAlpha:0];
|
| + [UIView animateWithDuration:kAnimationDuration
|
| + animations:^{
|
| + [self.maskView setAlpha:kMediumAlpha];
|
| + [self.shareView setAlpha:1];
|
| + }];
|
| +}
|
| +
|
| +- (void)loadElementsFromContext {
|
| + NSString* typeURL = static_cast<NSString*>(kUTTypeURL);
|
| + for (NSExtensionItem* item in self.extensionContext.inputItems) {
|
| + for (NSItemProvider* itemProvider in item.attachments) {
|
| + if ([itemProvider hasItemConformingToTypeIdentifier:typeURL]) {
|
| + ItemBlock URLCompletion = ^(id idURL, NSError* error) {
|
| + NSURL* URL = static_cast<NSURL*>(idURL);
|
| + dispatch_async(dispatch_get_main_queue(), ^{
|
| + _shareItem = [item copy];
|
| + _shareURL = [URL copy];
|
| + _shareTitle = [[[item attributedContentText] string] copy];
|
| + if ([_shareTitle length] == 0) {
|
| + _shareTitle = [URL host];
|
| + }
|
| + [self.shareView setURL:URL];
|
| + [self.shareView setTitle:_shareTitle];
|
| + });
|
| +
|
| + };
|
| + [itemProvider loadItemForTypeIdentifier:typeURL
|
| + options:nil
|
| + completionHandler:URLCompletion];
|
| + NSDictionary* imageOptions = @{
|
| + NSItemProviderPreferredImageSizeKey : [NSValue
|
| + valueWithCGSize:CGSizeMake(kScreenShotWidth, kScreenShotHeight)]
|
| + };
|
| + ItemBlock ImageCompletion = ^(id item, NSError* error) {
|
| +
|
| + UIImage* image = static_cast<UIImage*>(item);
|
| + if (image) {
|
| + dispatch_async(dispatch_get_main_queue(), ^{
|
| + [self.shareView setScreenshot:image];
|
| + });
|
| + }
|
| +
|
| + };
|
| + [itemProvider loadPreviewImageWithOptions:imageOptions
|
| + completionHandler:ImageCompletion];
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +- (void)dismissAndReturnItem:(NSExtensionItem*)item {
|
| + // Set the Y center constraints so the whole extension slides up out of the
|
| + // screen. Constant is relative to the center of the screen.
|
| + [_centerYConstraint setConstant:-(self.view.frame.size.height +
|
| + self.shareView.frame.size.height) /
|
| + 2];
|
| + [UIView animateWithDuration:kAnimationDuration
|
| + animations:^{
|
| + [self.maskView setAlpha:0];
|
| + [self.view layoutIfNeeded];
|
| + }
|
| + completion:^(BOOL finished) {
|
| + NSArray* returnItem = item ? @[ item ] : @[];
|
| + [self.extensionContext completeRequestReturningItems:returnItem
|
| + completionHandler:nil];
|
| + }];
|
| +}
|
| +
|
| +- (void)queueActionItemURL:(NSURL*)URL
|
| + title:(NSString*)title
|
| + action:(app_group::ShareExtensionItemType)actionType
|
| + cancel:(BOOL)cancel
|
| + completion:(ProceduralBlock)completion {
|
| + NSURL* readingListURL = app_group::ShareExtensionItemsFolder();
|
| + if (![[NSFileManager defaultManager]
|
| + fileExistsAtPath:[readingListURL path]]) {
|
| + [[NSFileManager defaultManager] createDirectoryAtPath:[readingListURL path]
|
| + withIntermediateDirectories:YES
|
| + attributes:nil
|
| + error:nil];
|
| + }
|
| + NSDate* date = [NSDate date];
|
| + NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
|
| + // This format sorts files by alphabetical order.
|
| + [dateFormatter setDateFormat:@"yyyy-MM-dd-HH-mm-ss.SSSSSS"];
|
| + NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
|
| + [dateFormatter setTimeZone:timeZone];
|
| + NSString* dateString = [dateFormatter stringFromDate:date];
|
| + NSURL* fileURL =
|
| + [readingListURL URLByAppendingPathComponent:dateString isDirectory:NO];
|
| +
|
| + NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
|
| + if (URL)
|
| + [dict setObject:URL forKey:app_group::kShareItemURL];
|
| + if (title)
|
| + [dict setObject:title forKey:app_group::kShareItemTitle];
|
| + [dict setObject:date forKey:app_group::kShareItemDate];
|
| +
|
| + if (!cancel) {
|
| + NSNumber* entryType = [NSNumber numberWithInteger:actionType];
|
| + [dict setObject:entryType forKey:app_group::kShareItemType];
|
| + }
|
| +
|
| + [dict setValue:[NSNumber numberWithBool:cancel]
|
| + forKey:app_group::kShareItemCancel];
|
| + NSData* data = [NSKeyedArchiver archivedDataWithRootObject:dict];
|
| + [[NSFileManager defaultManager] createFileAtPath:[fileURL path]
|
| + contents:data
|
| + attributes:nil];
|
| + if (completion) {
|
| + completion();
|
| + }
|
| +}
|
| +
|
| +#pragma mark - ShareExtensionViewActionTarget
|
| +
|
| +- (void)shareExtensionViewDidSelectCancel:(id)sender {
|
| + [self queueActionItemURL:nil
|
| + title:nil
|
| + action:app_group::READING_LIST_ITEM // Ignored
|
| + cancel:YES
|
| + completion:^{
|
| + [self dismissAndReturnItem:nil];
|
| + }];
|
| +}
|
| +
|
| +- (void)shareExtensionViewDidSelectAddToReadingList:(id)sender {
|
| + [self queueActionItemURL:_shareURL
|
| + title:_shareTitle
|
| + action:app_group::READING_LIST_ITEM
|
| + cancel:NO
|
| + completion:^{
|
| + [self dismissAndReturnItem:_shareItem];
|
| + }];
|
| +}
|
| +
|
| +- (void)shareExtensionViewDidSelectAddToBookmarks:(id)sender {
|
| + [self queueActionItemURL:_shareURL
|
| + title:_shareTitle
|
| + action:app_group::BOOKMARK_ITEM
|
| + cancel:NO
|
| + completion:^{
|
| + [self dismissAndReturnItem:_shareItem];
|
| + }];
|
| +}
|
| +
|
| +- (void)shareExtensionView:(id)sender
|
| + typeChanged:(app_group::ShareExtensionItemType)type {
|
| + [self setItemType:type];
|
| +}
|
| +
|
| +@end
|
|
|