Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1182)

Unified Diff: ios/chrome/browser/ui/settings/native_apps_collection_view_controller.mm

Issue 2587023002: Upstream Chrome on iOS source code [8/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/settings/native_apps_collection_view_controller.mm
diff --git a/ios/chrome/browser/ui/settings/native_apps_collection_view_controller.mm b/ios/chrome/browser/ui/settings/native_apps_collection_view_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..ff8cdad922bf89115353ab988239004ad95ae49a
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/native_apps_collection_view_controller.mm
@@ -0,0 +1,400 @@
+// Copyright 2015 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 "ios/chrome/browser/ui/settings/native_apps_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/native_apps_collection_view_controller_private.h"
+
+#import <StoreKit/StoreKit.h>
+
+#include "base/ios/weak_nsobject.h"
+#include "base/logging.h"
+#import "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
+#include "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/installation_notifier.h"
+#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
+#import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
+#import "ios/chrome/browser/ui/settings/cells/native_app_item.h"
+#import "ios/chrome/browser/ui/settings/settings_utils.h"
+#include "ios/chrome/common/string_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metadata.h"
+#import "ios/public/provider/chrome/browser/native_app_launcher/native_app_whitelist_manager.h"
+#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+const NSInteger kTagShift = 1000;
+
+namespace {
+
+typedef NS_ENUM(NSInteger, SectionIdentifier) {
+ SectionIdentifierLearnMore = kSectionIdentifierEnumZero,
+ SectionIdentifierApps,
+};
+
+typedef NS_ENUM(NSInteger, ItemType) {
+ ItemTypeApp = kItemTypeEnumZero,
+ ItemTypeLearnMore,
+};
+
+} // namespace
+
+@interface NativeAppsCollectionViewController ()<
+ SKStoreProductViewControllerDelegate> {
+ net::URLRequestContextGetter* _requestContextGetter; // weak
+ base::scoped_nsobject<NSArray> _nativeAppsInSettings;
+ BOOL _userDidSomething;
+}
+
+// List of the native apps visible in Settings.
+@property(nonatomic, copy) NSArray* appsInSettings;
+
+// Delegate for App-Store-related operations.
+@property(nonatomic, assign) id<StoreKitLauncher> storeKitLauncher;
+
+// Sets up the list of visible apps based on |nativeAppWhitelistManager|, which
+// serves as datasource for this controller. Apps from
+// |nativeAppWhitelistManager| are stored in |_nativeAppsInSettings| and
+// |-reloadData| is sent to the receiver.
+- (void)configureWithNativeAppWhiteListManager:
+ (id<NativeAppWhitelistManager>)nativeAppWhitelistManager;
+
+// Returns a new Native App collection view item for the metadata at |index| in
+// |_nativeAppsInSettings|.
+- (CollectionViewItem*)nativeAppItemAtIndex:(NSUInteger)index;
+
+// Target method for the auto open in app switch.
+// Called when an auto-open-in-app switch is toggled.
+- (void)autoOpenInAppChanged:(UISwitch*)switchControl;
+
+// Called when an Install button is being tapped.
+- (void)installApp:(UIButton*)button;
+
+// Called when an app with the registered scheme is opened.
+- (void)appDidInstall:(NSNotification*)notification;
+
+// Returns the app at |index| in the list of visible apps.
+- (id<NativeAppMetadata>)nativeAppAtIndex:(NSUInteger)index;
+
+// Records a user action in UMA under NativeAppLauncher.Settings.
+// If this method is not called during the lifetime of the view,
+// |settings::kNativeAppsActionDidNothing| is recorded in UMA.
+- (void)recordUserAction:(settings::NativeAppsAction)action;
+
+@end
+
+@implementation NativeAppsCollectionViewController
+
+@synthesize storeKitLauncher = _storeKitLauncher;
+
+- (id)initWithURLRequestContextGetter:
+ (net::URLRequestContextGetter*)requestContextGetter {
+ self = [super initWithStyle:CollectionViewControllerStyleAppBar];
+ if (self) {
+ _requestContextGetter = requestContextGetter;
+ base::RecordAction(base::UserMetricsAction("MobileGALOpenSettings"));
+ _storeKitLauncher = self;
+
+ [self loadModel];
+ }
+ return self;
+}
+
+#pragma mark - View lifecycle
+
+- (void)viewDidLoad {
+ self.title = l10n_util::GetNSString(IDS_IOS_GOOGLE_APPS_SM_SETTINGS);
+ [self configureWithNativeAppWhiteListManager:
+ ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager()];
+
+ [super viewDidLoad];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+ [[InstallationNotifier sharedInstance] checkNow];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(reloadData)
+ name:UIApplicationDidBecomeActiveNotification
+ object:nil];
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+ [super viewDidDisappear:animated];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:UIApplicationDidBecomeActiveNotification
+ object:nil];
+ if ([self isMovingFromParentViewController]) {
+ // The view controller is popped.
+ [[InstallationNotifier sharedInstance] unregisterForNotifications:self];
+ if (!_userDidSomething)
+ [self recordUserAction:settings::kNativeAppsActionDidNothing];
+ }
+}
+
+#pragma mark - CollectionViewController
+
+- (void)loadModel {
+ [super loadModel];
+ CollectionViewModel* model = self.collectionViewModel;
+ NSUInteger appsCount = [_nativeAppsInSettings count];
+
+ [model addSectionWithIdentifier:SectionIdentifierLearnMore];
+ [model addItem:[self learnMoreItem]
+ toSectionWithIdentifier:SectionIdentifierLearnMore];
+
+ [model addSectionWithIdentifier:SectionIdentifierApps];
+
+ for (NSUInteger i = 0; i < appsCount; i++) {
+ [model addItem:[self nativeAppItemAtIndex:i]
+ toSectionWithIdentifier:SectionIdentifierApps];
+ }
+}
+
+- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
+ cellForItemAtIndexPath:(NSIndexPath*)indexPath {
+ UICollectionViewCell* cell =
+ [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
+ if ([self.collectionViewModel
+ sectionIdentifierForSection:indexPath.section] ==
+ SectionIdentifierApps) {
+ NativeAppCell* appCell = base::mac::ObjCCastStrict<NativeAppCell>(cell);
+ [self configureNativeAppCell:appCell atIndexPath:indexPath];
+ }
+ return cell;
+}
+
+#pragma mark - MDCCollectionViewStylingDelegate
+
+- (CGFloat)collectionView:(nonnull UICollectionView*)collectionView
+ cellHeightAtIndexPath:(nonnull NSIndexPath*)indexPath {
+ CollectionViewItem* item =
+ [self.collectionViewModel itemAtIndexPath:indexPath];
+ switch (item.type) {
+ case ItemTypeLearnMore:
+ return [MDCCollectionViewCell
+ cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
+ forItem:item];
+ case ItemTypeApp:
+ return MDCCellDefaultOneLineWithAvatarHeight;
+ default:
+ return MDCCellDefaultOneLineHeight;
+ }
+}
+
+- (void)configureNativeAppCell:(NativeAppCell*)appCell
+ atIndexPath:(NSIndexPath*)indexPath {
+ appCell.switchControl.tag = [self tagForIndexPath:indexPath];
+ [appCell.switchControl addTarget:self
+ action:@selector(autoOpenInAppChanged:)
+ forControlEvents:UIControlEventValueChanged];
+ appCell.installButton.tag = [self tagForIndexPath:indexPath];
+ [appCell.installButton addTarget:self
+ action:@selector(installApp:)
+ forControlEvents:UIControlEventTouchUpInside];
+ CollectionViewItem* item =
+ [self.collectionViewModel itemAtIndexPath:indexPath];
+ NativeAppItem* appItem = base::mac::ObjCCastStrict<NativeAppItem>(item);
+ if (!appItem.icon) {
+ // Fetch the real icon.
+ base::WeakNSObject<NativeAppsCollectionViewController> weakSelf(self);
+ id<NativeAppMetadata> metadata = [self nativeAppAtIndex:indexPath.item];
+ [metadata
+ fetchSmallIconWithContext:_requestContextGetter
+ completionBlock:^(UIImage* image) {
+ base::scoped_nsobject<NativeAppsCollectionViewController>
+ strongSelf([weakSelf retain]);
+ if (!image || !strongSelf)
+ return;
+ appItem.icon = image;
+ [strongSelf.get().collectionView
+ reloadItemsAtIndexPaths:@[ indexPath ]];
+ }];
+ }
+}
+
+- (CollectionViewItem*)learnMoreItem {
+ NSString* learnMoreText =
+ l10n_util::GetNSString(IDS_IOS_GOOGLE_APPS_SM_SECTION_HEADER);
+ CollectionViewFooterItem* learnMoreItem = [[[CollectionViewFooterItem alloc]
+ initWithType:ItemTypeLearnMore] autorelease];
+ learnMoreItem.text = learnMoreText;
+ return learnMoreItem;
+}
+
+#pragma mark - SKStoreProductViewControllerDelegate methods
+
+- (void)productViewControllerDidFinish:
+ (SKStoreProductViewController*)viewController {
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+#pragma mark - StoreKitLauncher methods
+
+- (void)openAppStore:(NSString*)appId {
+ // Reported crashes show that -openAppStore: had been called with
+ // a nil |appId|, but opening AppStore is meaningful only if the |appId| is
+ // not nil, so be defensive and early return if |appId| is nil.
+ if (![appId length])
+ return;
+ NSDictionary* product =
+ @{SKStoreProductParameterITunesItemIdentifier : appId};
+ base::scoped_nsobject<SKStoreProductViewController> storeViewController(
+ [[SKStoreProductViewController alloc] init]);
+ [storeViewController setDelegate:self];
+ [storeViewController loadProductWithParameters:product completionBlock:nil];
+ [self presentViewController:storeViewController animated:YES completion:nil];
+}
+
+#pragma mark - MDCCollectionViewStylingDelegate
+
+// MDCCollectionViewStylingDelegate protocol is implemented so that cells don't
+// display ink on touch.
+- (BOOL)collectionView:(nonnull UICollectionView*)collectionView
+ hidesInkViewAtIndexPath:(nonnull NSIndexPath*)indexPath {
+ return YES;
+}
+
+- (MDCCollectionViewCellStyle)collectionView:(UICollectionView*)collectionView
+ cellStyleForSection:(NSInteger)section {
+ NSInteger sectionIdentifier =
+ [self.collectionViewModel sectionIdentifierForSection:section];
+ switch (sectionIdentifier) {
+ case SectionIdentifierLearnMore:
+ // Display the Learn More footer in the default style with no "card" UI
+ // and no section padding.
+ return MDCCollectionViewCellStyleDefault;
+ default:
+ return self.styler.cellStyle;
+ }
+}
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath {
+ NSInteger sectionIdentifier =
+ [self.collectionViewModel sectionIdentifierForSection:indexPath.section];
+ switch (sectionIdentifier) {
+ case SectionIdentifierLearnMore:
+ // Display the Learn More footer without any background image or
+ // shadowing.
+ return YES;
+ default:
+ return NO;
+ }
+}
+#pragma mark - Private methods
+
+- (void)autoOpenInAppChanged:(UISwitch*)switchControl {
+ NSInteger index = [self indexPathForTag:switchControl.tag].item;
+ id<NativeAppMetadata> metadata = [self nativeAppAtIndex:index];
+ DCHECK([metadata isInstalled]);
+ BOOL autoOpenOn = switchControl.on;
+ metadata.shouldAutoOpenLinks = autoOpenOn;
+ [self recordUserAction:(autoOpenOn
+ ? settings::kNativeAppsActionTurnedAutoOpenOn
+ : settings::kNativeAppsActionTurnedAutoOpenOff)];
+}
+
+- (void)installApp:(UIButton*)button {
+ [self recordUserAction:settings::kNativeAppsActionClickedInstall];
+ NSInteger index = [self indexPathForTag:button.tag].item;
+ id<NativeAppMetadata> metadata = [self nativeAppAtIndex:index];
+ DCHECK(![metadata isInstalled]);
+ [metadata updateCounterWithAppInstallation];
+
+ // Register to get a notification when the app is installed.
+ [[InstallationNotifier sharedInstance]
+ registerForInstallationNotifications:self
+ withSelector:@selector(appDidInstall:)
+ forScheme:[metadata anyScheme]];
+ [self.storeKitLauncher openAppStore:[metadata appId]];
+}
+
+- (void)appDidInstall:(NSNotification*)notification {
+ // The name of the notification is the scheme of the new app installed.
+ GURL url(base::SysNSStringToUTF8([notification name]) + ":");
+ DCHECK(url.is_valid());
+ NSUInteger matchingAppIndex = [_nativeAppsInSettings
+ indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL* stop) {
+ id<NativeAppMetadata> metadata =
+ static_cast<id<NativeAppMetadata>>(obj);
+ return [metadata canOpenURL:url];
+ }];
+ [[self nativeAppAtIndex:matchingAppIndex] setShouldAutoOpenLinks:YES];
+ [self reloadData];
+}
+
+- (void)configureWithNativeAppWhiteListManager:
+ (id<NativeAppWhitelistManager>)nativeAppWhitelistManager {
+ NSArray* allApps = [nativeAppWhitelistManager
+ filteredAppsUsingBlock:^(const id<NativeAppMetadata> app, BOOL* stop) {
+ return [app isGoogleOwnedApp];
+ }];
+ [self setAppsInSettings:allApps];
+ [self reloadData];
+}
+
+- (id<NativeAppMetadata>)nativeAppAtIndex:(NSUInteger)index {
+ id<NativeAppMetadata> metadata = [_nativeAppsInSettings objectAtIndex:index];
+ DCHECK([metadata conformsToProtocol:@protocol(NativeAppMetadata)]);
+ return metadata;
+}
+
+- (void)recordUserAction:(settings::NativeAppsAction)action {
+ _userDidSomething = YES;
+ UMA_HISTOGRAM_ENUMERATION("NativeAppLauncher.Settings", action,
+ settings::kNativeAppsActionCount);
+}
+
+- (CollectionViewItem*)nativeAppItemAtIndex:(NSUInteger)index {
+ id<NativeAppMetadata> metadata = [self nativeAppAtIndex:index];
+ // Determine the state of the cell.
+ NativeAppItemState state;
+ if ([metadata isInstalled]) {
+ state = [metadata shouldAutoOpenLinks] ? NativeAppItemSwitchOn
+ : NativeAppItemSwitchOff;
+ } else {
+ state = NativeAppItemInstall;
+ }
+ NativeAppItem* appItem =
+ [[[NativeAppItem alloc] initWithType:ItemTypeApp] autorelease];
+ appItem.name = [metadata appName];
+ appItem.state = state;
+ return appItem;
+}
+
+- (NSArray*)appsInSettings {
+ return _nativeAppsInSettings.get();
+}
+
+- (void)setAppsInSettings:(NSArray*)apps {
+ _nativeAppsInSettings.reset([apps copy]);
+}
+
+- (NSInteger)tagForIndexPath:(NSIndexPath*)indexPath {
+ DCHECK(indexPath.section ==
+ [self.collectionViewModel
+ sectionForSectionIdentifier:SectionIdentifierApps]);
+ return indexPath.item + kTagShift;
+}
+
+- (NSIndexPath*)indexPathForTag:(NSInteger)shiftedTag {
+ NSInteger unshiftedTag = shiftedTag - kTagShift;
+ return [NSIndexPath
+ indexPathForItem:unshiftedTag
+ inSection:[self.collectionViewModel
+ sectionForSectionIdentifier:SectionIdentifierApps]];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698