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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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 "ios/chrome/browser/ui/settings/native_apps_collection_view_controller.h "
6 #import "ios/chrome/browser/ui/settings/native_apps_collection_view_controller_p rivate.h"
7
8 #import <StoreKit/StoreKit.h>
9
10 #include "base/ios/weak_nsobject.h"
11 #include "base/logging.h"
12 #import "base/mac/foundation_util.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/metrics/histogram.h"
15 #include "base/metrics/user_metrics.h"
16 #include "base/metrics/user_metrics_action.h"
17 #include "base/strings/sys_string_conversions.h"
18 #import "ios/chrome/browser/installation_notifier.h"
19 #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrom e.h"
20 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item .h"
21 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
22 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
23 #import "ios/chrome/browser/ui/settings/cells/native_app_item.h"
24 #import "ios/chrome/browser/ui/settings/settings_utils.h"
25 #include "ios/chrome/common/string_util.h"
26 #include "ios/chrome/grit/ios_strings.h"
27 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
28 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metad ata.h"
29 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_white list_manager.h"
30 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate rialButtons.h"
31 #include "net/url_request/url_request_context_getter.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "url/gurl.h"
34
35 const NSInteger kTagShift = 1000;
36
37 namespace {
38
39 typedef NS_ENUM(NSInteger, SectionIdentifier) {
40 SectionIdentifierLearnMore = kSectionIdentifierEnumZero,
41 SectionIdentifierApps,
42 };
43
44 typedef NS_ENUM(NSInteger, ItemType) {
45 ItemTypeApp = kItemTypeEnumZero,
46 ItemTypeLearnMore,
47 };
48
49 } // namespace
50
51 @interface NativeAppsCollectionViewController ()<
52 SKStoreProductViewControllerDelegate> {
53 net::URLRequestContextGetter* _requestContextGetter; // weak
54 base::scoped_nsobject<NSArray> _nativeAppsInSettings;
55 BOOL _userDidSomething;
56 }
57
58 // List of the native apps visible in Settings.
59 @property(nonatomic, copy) NSArray* appsInSettings;
60
61 // Delegate for App-Store-related operations.
62 @property(nonatomic, assign) id<StoreKitLauncher> storeKitLauncher;
63
64 // Sets up the list of visible apps based on |nativeAppWhitelistManager|, which
65 // serves as datasource for this controller. Apps from
66 // |nativeAppWhitelistManager| are stored in |_nativeAppsInSettings| and
67 // |-reloadData| is sent to the receiver.
68 - (void)configureWithNativeAppWhiteListManager:
69 (id<NativeAppWhitelistManager>)nativeAppWhitelistManager;
70
71 // Returns a new Native App collection view item for the metadata at |index| in
72 // |_nativeAppsInSettings|.
73 - (CollectionViewItem*)nativeAppItemAtIndex:(NSUInteger)index;
74
75 // Target method for the auto open in app switch.
76 // Called when an auto-open-in-app switch is toggled.
77 - (void)autoOpenInAppChanged:(UISwitch*)switchControl;
78
79 // Called when an Install button is being tapped.
80 - (void)installApp:(UIButton*)button;
81
82 // Called when an app with the registered scheme is opened.
83 - (void)appDidInstall:(NSNotification*)notification;
84
85 // Returns the app at |index| in the list of visible apps.
86 - (id<NativeAppMetadata>)nativeAppAtIndex:(NSUInteger)index;
87
88 // Records a user action in UMA under NativeAppLauncher.Settings.
89 // If this method is not called during the lifetime of the view,
90 // |settings::kNativeAppsActionDidNothing| is recorded in UMA.
91 - (void)recordUserAction:(settings::NativeAppsAction)action;
92
93 @end
94
95 @implementation NativeAppsCollectionViewController
96
97 @synthesize storeKitLauncher = _storeKitLauncher;
98
99 - (id)initWithURLRequestContextGetter:
100 (net::URLRequestContextGetter*)requestContextGetter {
101 self = [super initWithStyle:CollectionViewControllerStyleAppBar];
102 if (self) {
103 _requestContextGetter = requestContextGetter;
104 base::RecordAction(base::UserMetricsAction("MobileGALOpenSettings"));
105 _storeKitLauncher = self;
106
107 [self loadModel];
108 }
109 return self;
110 }
111
112 #pragma mark - View lifecycle
113
114 - (void)viewDidLoad {
115 self.title = l10n_util::GetNSString(IDS_IOS_GOOGLE_APPS_SM_SETTINGS);
116 [self configureWithNativeAppWhiteListManager:
117 ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager()];
118
119 [super viewDidLoad];
120 }
121
122 - (void)viewWillAppear:(BOOL)animated {
123 [super viewWillAppear:animated];
124 [[InstallationNotifier sharedInstance] checkNow];
125 [[NSNotificationCenter defaultCenter]
126 addObserver:self
127 selector:@selector(reloadData)
128 name:UIApplicationDidBecomeActiveNotification
129 object:nil];
130 }
131
132 - (void)viewDidDisappear:(BOOL)animated {
133 [super viewDidDisappear:animated];
134 [[NSNotificationCenter defaultCenter]
135 removeObserver:self
136 name:UIApplicationDidBecomeActiveNotification
137 object:nil];
138 if ([self isMovingFromParentViewController]) {
139 // The view controller is popped.
140 [[InstallationNotifier sharedInstance] unregisterForNotifications:self];
141 if (!_userDidSomething)
142 [self recordUserAction:settings::kNativeAppsActionDidNothing];
143 }
144 }
145
146 #pragma mark - CollectionViewController
147
148 - (void)loadModel {
149 [super loadModel];
150 CollectionViewModel* model = self.collectionViewModel;
151 NSUInteger appsCount = [_nativeAppsInSettings count];
152
153 [model addSectionWithIdentifier:SectionIdentifierLearnMore];
154 [model addItem:[self learnMoreItem]
155 toSectionWithIdentifier:SectionIdentifierLearnMore];
156
157 [model addSectionWithIdentifier:SectionIdentifierApps];
158
159 for (NSUInteger i = 0; i < appsCount; i++) {
160 [model addItem:[self nativeAppItemAtIndex:i]
161 toSectionWithIdentifier:SectionIdentifierApps];
162 }
163 }
164
165 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
166 cellForItemAtIndexPath:(NSIndexPath*)indexPath {
167 UICollectionViewCell* cell =
168 [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
169 if ([self.collectionViewModel
170 sectionIdentifierForSection:indexPath.section] ==
171 SectionIdentifierApps) {
172 NativeAppCell* appCell = base::mac::ObjCCastStrict<NativeAppCell>(cell);
173 [self configureNativeAppCell:appCell atIndexPath:indexPath];
174 }
175 return cell;
176 }
177
178 #pragma mark - MDCCollectionViewStylingDelegate
179
180 - (CGFloat)collectionView:(nonnull UICollectionView*)collectionView
181 cellHeightAtIndexPath:(nonnull NSIndexPath*)indexPath {
182 CollectionViewItem* item =
183 [self.collectionViewModel itemAtIndexPath:indexPath];
184 switch (item.type) {
185 case ItemTypeLearnMore:
186 return [MDCCollectionViewCell
187 cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
188 forItem:item];
189 case ItemTypeApp:
190 return MDCCellDefaultOneLineWithAvatarHeight;
191 default:
192 return MDCCellDefaultOneLineHeight;
193 }
194 }
195
196 - (void)configureNativeAppCell:(NativeAppCell*)appCell
197 atIndexPath:(NSIndexPath*)indexPath {
198 appCell.switchControl.tag = [self tagForIndexPath:indexPath];
199 [appCell.switchControl addTarget:self
200 action:@selector(autoOpenInAppChanged:)
201 forControlEvents:UIControlEventValueChanged];
202 appCell.installButton.tag = [self tagForIndexPath:indexPath];
203 [appCell.installButton addTarget:self
204 action:@selector(installApp:)
205 forControlEvents:UIControlEventTouchUpInside];
206 CollectionViewItem* item =
207 [self.collectionViewModel itemAtIndexPath:indexPath];
208 NativeAppItem* appItem = base::mac::ObjCCastStrict<NativeAppItem>(item);
209 if (!appItem.icon) {
210 // Fetch the real icon.
211 base::WeakNSObject<NativeAppsCollectionViewController> weakSelf(self);
212 id<NativeAppMetadata> metadata = [self nativeAppAtIndex:indexPath.item];
213 [metadata
214 fetchSmallIconWithContext:_requestContextGetter
215 completionBlock:^(UIImage* image) {
216 base::scoped_nsobject<NativeAppsCollectionViewController>
217 strongSelf([weakSelf retain]);
218 if (!image || !strongSelf)
219 return;
220 appItem.icon = image;
221 [strongSelf.get().collectionView
222 reloadItemsAtIndexPaths:@[ indexPath ]];
223 }];
224 }
225 }
226
227 - (CollectionViewItem*)learnMoreItem {
228 NSString* learnMoreText =
229 l10n_util::GetNSString(IDS_IOS_GOOGLE_APPS_SM_SECTION_HEADER);
230 CollectionViewFooterItem* learnMoreItem = [[[CollectionViewFooterItem alloc]
231 initWithType:ItemTypeLearnMore] autorelease];
232 learnMoreItem.text = learnMoreText;
233 return learnMoreItem;
234 }
235
236 #pragma mark - SKStoreProductViewControllerDelegate methods
237
238 - (void)productViewControllerDidFinish:
239 (SKStoreProductViewController*)viewController {
240 [self dismissViewControllerAnimated:YES completion:nil];
241 }
242
243 #pragma mark - StoreKitLauncher methods
244
245 - (void)openAppStore:(NSString*)appId {
246 // Reported crashes show that -openAppStore: had been called with
247 // a nil |appId|, but opening AppStore is meaningful only if the |appId| is
248 // not nil, so be defensive and early return if |appId| is nil.
249 if (![appId length])
250 return;
251 NSDictionary* product =
252 @{SKStoreProductParameterITunesItemIdentifier : appId};
253 base::scoped_nsobject<SKStoreProductViewController> storeViewController(
254 [[SKStoreProductViewController alloc] init]);
255 [storeViewController setDelegate:self];
256 [storeViewController loadProductWithParameters:product completionBlock:nil];
257 [self presentViewController:storeViewController animated:YES completion:nil];
258 }
259
260 #pragma mark - MDCCollectionViewStylingDelegate
261
262 // MDCCollectionViewStylingDelegate protocol is implemented so that cells don't
263 // display ink on touch.
264 - (BOOL)collectionView:(nonnull UICollectionView*)collectionView
265 hidesInkViewAtIndexPath:(nonnull NSIndexPath*)indexPath {
266 return YES;
267 }
268
269 - (MDCCollectionViewCellStyle)collectionView:(UICollectionView*)collectionView
270 cellStyleForSection:(NSInteger)section {
271 NSInteger sectionIdentifier =
272 [self.collectionViewModel sectionIdentifierForSection:section];
273 switch (sectionIdentifier) {
274 case SectionIdentifierLearnMore:
275 // Display the Learn More footer in the default style with no "card" UI
276 // and no section padding.
277 return MDCCollectionViewCellStyleDefault;
278 default:
279 return self.styler.cellStyle;
280 }
281 }
282
283 - (BOOL)collectionView:(UICollectionView*)collectionView
284 shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath {
285 NSInteger sectionIdentifier =
286 [self.collectionViewModel sectionIdentifierForSection:indexPath.section];
287 switch (sectionIdentifier) {
288 case SectionIdentifierLearnMore:
289 // Display the Learn More footer without any background image or
290 // shadowing.
291 return YES;
292 default:
293 return NO;
294 }
295 }
296 #pragma mark - Private methods
297
298 - (void)autoOpenInAppChanged:(UISwitch*)switchControl {
299 NSInteger index = [self indexPathForTag:switchControl.tag].item;
300 id<NativeAppMetadata> metadata = [self nativeAppAtIndex:index];
301 DCHECK([metadata isInstalled]);
302 BOOL autoOpenOn = switchControl.on;
303 metadata.shouldAutoOpenLinks = autoOpenOn;
304 [self recordUserAction:(autoOpenOn
305 ? settings::kNativeAppsActionTurnedAutoOpenOn
306 : settings::kNativeAppsActionTurnedAutoOpenOff)];
307 }
308
309 - (void)installApp:(UIButton*)button {
310 [self recordUserAction:settings::kNativeAppsActionClickedInstall];
311 NSInteger index = [self indexPathForTag:button.tag].item;
312 id<NativeAppMetadata> metadata = [self nativeAppAtIndex:index];
313 DCHECK(![metadata isInstalled]);
314 [metadata updateCounterWithAppInstallation];
315
316 // Register to get a notification when the app is installed.
317 [[InstallationNotifier sharedInstance]
318 registerForInstallationNotifications:self
319 withSelector:@selector(appDidInstall:)
320 forScheme:[metadata anyScheme]];
321 [self.storeKitLauncher openAppStore:[metadata appId]];
322 }
323
324 - (void)appDidInstall:(NSNotification*)notification {
325 // The name of the notification is the scheme of the new app installed.
326 GURL url(base::SysNSStringToUTF8([notification name]) + ":");
327 DCHECK(url.is_valid());
328 NSUInteger matchingAppIndex = [_nativeAppsInSettings
329 indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL* stop) {
330 id<NativeAppMetadata> metadata =
331 static_cast<id<NativeAppMetadata>>(obj);
332 return [metadata canOpenURL:url];
333 }];
334 [[self nativeAppAtIndex:matchingAppIndex] setShouldAutoOpenLinks:YES];
335 [self reloadData];
336 }
337
338 - (void)configureWithNativeAppWhiteListManager:
339 (id<NativeAppWhitelistManager>)nativeAppWhitelistManager {
340 NSArray* allApps = [nativeAppWhitelistManager
341 filteredAppsUsingBlock:^(const id<NativeAppMetadata> app, BOOL* stop) {
342 return [app isGoogleOwnedApp];
343 }];
344 [self setAppsInSettings:allApps];
345 [self reloadData];
346 }
347
348 - (id<NativeAppMetadata>)nativeAppAtIndex:(NSUInteger)index {
349 id<NativeAppMetadata> metadata = [_nativeAppsInSettings objectAtIndex:index];
350 DCHECK([metadata conformsToProtocol:@protocol(NativeAppMetadata)]);
351 return metadata;
352 }
353
354 - (void)recordUserAction:(settings::NativeAppsAction)action {
355 _userDidSomething = YES;
356 UMA_HISTOGRAM_ENUMERATION("NativeAppLauncher.Settings", action,
357 settings::kNativeAppsActionCount);
358 }
359
360 - (CollectionViewItem*)nativeAppItemAtIndex:(NSUInteger)index {
361 id<NativeAppMetadata> metadata = [self nativeAppAtIndex:index];
362 // Determine the state of the cell.
363 NativeAppItemState state;
364 if ([metadata isInstalled]) {
365 state = [metadata shouldAutoOpenLinks] ? NativeAppItemSwitchOn
366 : NativeAppItemSwitchOff;
367 } else {
368 state = NativeAppItemInstall;
369 }
370 NativeAppItem* appItem =
371 [[[NativeAppItem alloc] initWithType:ItemTypeApp] autorelease];
372 appItem.name = [metadata appName];
373 appItem.state = state;
374 return appItem;
375 }
376
377 - (NSArray*)appsInSettings {
378 return _nativeAppsInSettings.get();
379 }
380
381 - (void)setAppsInSettings:(NSArray*)apps {
382 _nativeAppsInSettings.reset([apps copy]);
383 }
384
385 - (NSInteger)tagForIndexPath:(NSIndexPath*)indexPath {
386 DCHECK(indexPath.section ==
387 [self.collectionViewModel
388 sectionForSectionIdentifier:SectionIdentifierApps]);
389 return indexPath.item + kTagShift;
390 }
391
392 - (NSIndexPath*)indexPathForTag:(NSInteger)shiftedTag {
393 NSInteger unshiftedTag = shiftedTag - kTagShift;
394 return [NSIndexPath
395 indexPathForItem:unshiftedTag
396 inSection:[self.collectionViewModel
397 sectionForSectionIdentifier:SectionIdentifierApps]];
398 }
399
400 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698