content_widget_view_controller.mm 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // Copyright 2017 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. #import "ios/chrome/content_widget_extension/content_widget_view_controller.h"
  5. #include "base/mac/foundation_util.h"
  6. #include "base/strings/sys_string_conversions.h"
  7. #include "ios/chrome/common/app_group/app_group_command.h"
  8. #include "ios/chrome/common/app_group/app_group_constants.h"
  9. #include "ios/chrome/common/app_group/app_group_metrics.h"
  10. #import "ios/chrome/common/crash_report/crash_helper.h"
  11. #import "ios/chrome/common/ntp_tile/ntp_tile.h"
  12. #import "ios/chrome/common/ui/util/constraints_ui_util.h"
  13. #include "ios/chrome/content_widget_extension/content_widget_view.h"
  14. #import "ios/chrome/content_widget_extension/most_visited_tile_view.h"
  15. #if !defined(__has_feature) || !__has_feature(objc_arc)
  16. #error "This file requires ARC support."
  17. #endif
  18. namespace {
  19. // Using GURL in the extension is not wanted as it includes ICU which makes the
  20. // extension binary much larger; therefore, ios/chrome/common/x_callback_url.h
  21. // cannot be used. This class makes a very basic use of x-callback-url, so no
  22. // full implementation is required.
  23. NSString* const kXCallbackURLHost = @"x-callback-url";
  24. } // namespace
  25. @interface ContentWidgetViewController ()<ContentWidgetViewDelegate>
  26. @property(nonatomic, strong) NSDictionary<NSURL*, NTPTile*>* sites;
  27. @property(nonatomic, weak) ContentWidgetView* widgetView;
  28. @property(nonatomic, readonly) BOOL isCompact;
  29. // Updates the widget with latest data. Returns whether any visual updates
  30. // occurred.
  31. - (BOOL)updateWidget;
  32. // Expand the widget.
  33. - (void)setExpanded:(CGSize)maxSize;
  34. // Register a display of the widget in the app_group NSUserDefaults.
  35. // Metrics on the widget usage will be sent (if enabled) on the next Chrome
  36. // startup.
  37. - (void)registerWidgetDisplay;
  38. @end
  39. @implementation ContentWidgetViewController
  40. @synthesize sites = _sites;
  41. @synthesize widgetView = _widgetView;
  42. #pragma mark - properties
  43. - (BOOL)isCompact {
  44. DCHECK(self.extensionContext);
  45. return [self.extensionContext widgetActiveDisplayMode] ==
  46. NCWidgetDisplayModeCompact;
  47. return NO;
  48. }
  49. #pragma mark - UIViewController
  50. + (void)initialize {
  51. if (self == [ContentWidgetViewController self]) {
  52. if (crash_helper::common::CanUseCrashpad()) {
  53. crash_helper::common::StartCrashpad();
  54. }
  55. }
  56. }
  57. - (void)viewDidLoad {
  58. [super viewDidLoad];
  59. DCHECK(self.extensionContext);
  60. CGFloat height =
  61. [self.extensionContext
  62. widgetMaximumSizeForDisplayMode:NCWidgetDisplayModeCompact]
  63. .height;
  64. CGFloat width =
  65. [self.extensionContext
  66. widgetMaximumSizeForDisplayMode:NCWidgetDisplayModeCompact]
  67. .width;
  68. // A local variable is necessary here as the property is declared weak and the
  69. // object would be deallocated before being retained by the addSubview call.
  70. ContentWidgetView* widgetView =
  71. [[ContentWidgetView alloc] initWithDelegate:self
  72. compactHeight:height
  73. width:width];
  74. self.widgetView = widgetView;
  75. [self.view addSubview:self.widgetView];
  76. self.extensionContext.widgetLargestAvailableDisplayMode =
  77. NCWidgetDisplayModeCompact;
  78. self.widgetView.translatesAutoresizingMaskIntoConstraints = NO;
  79. AddSameConstraints(self.widgetView, self.view);
  80. }
  81. - (void)viewWillAppear:(BOOL)animated {
  82. [super viewWillAppear:animated];
  83. [self registerWidgetDisplay];
  84. // |widgetActiveDisplayMode| does not contain a valid value in viewDidLoad. By
  85. // the time viewWillAppear is called, it is correct, so set the mode here.
  86. [self.widgetView showMode:self.isCompact];
  87. }
  88. - (void)widgetPerformUpdateWithCompletionHandler:
  89. (void (^)(NCUpdateResult))completionHandler {
  90. completionHandler([self updateWidget] ? NCUpdateResultNewData
  91. : NCUpdateResultNoData);
  92. }
  93. - (void)viewWillTransitionToSize:(CGSize)size
  94. withTransitionCoordinator:
  95. (id<UIViewControllerTransitionCoordinator>)coordinator {
  96. [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  97. [coordinator
  98. animateAlongsideTransition:^(
  99. id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
  100. [self.widgetView showMode:self.isCompact];
  101. [self.widgetView layoutIfNeeded];
  102. }
  103. completion:nil];
  104. }
  105. #pragma mark - NCWidgetProviding
  106. - (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode
  107. withMaximumSize:(CGSize)maxSize
  108. API_AVAILABLE(ios(10.0)) {
  109. switch (activeDisplayMode) {
  110. case NCWidgetDisplayModeCompact:
  111. self.preferredContentSize = maxSize;
  112. break;
  113. case NCWidgetDisplayModeExpanded:
  114. [self setExpanded:maxSize];
  115. break;
  116. }
  117. }
  118. #pragma mark - internal
  119. - (void)setExpanded:(CGSize)maxSize {
  120. [self updateWidget];
  121. self.preferredContentSize =
  122. CGSizeMake(maxSize.width,
  123. MIN([self.widgetView widgetExpandedHeight], maxSize.height));
  124. }
  125. - (BOOL)updateWidget {
  126. NSUserDefaults* sharedDefaults = app_group::GetGroupUserDefaults();
  127. NSData* data = [sharedDefaults objectForKey:app_group::kSuggestedItems];
  128. NSError* error = nil;
  129. NSKeyedUnarchiver* unarchiver =
  130. [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&error];
  131. if (!unarchiver || error) {
  132. DLOG(WARNING) << "Error creating unarchiver for widget extension: "
  133. << base::SysNSStringToUTF8([error description]);
  134. return NO;
  135. }
  136. unarchiver.requiresSecureCoding = NO;
  137. NSDictionary<NSURL*, NTPTile*>* newSites =
  138. [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey];
  139. if ([newSites isEqualToDictionary:self.sites]) {
  140. return NO;
  141. }
  142. self.sites = newSites;
  143. [self.widgetView updateSites:self.sites];
  144. self.extensionContext.widgetLargestAvailableDisplayMode =
  145. [self.widgetView sitesFitSingleRow] ? NCWidgetDisplayModeCompact
  146. : NCWidgetDisplayModeExpanded;
  147. return YES;
  148. }
  149. - (void)registerWidgetDisplay {
  150. NSUserDefaults* sharedDefaults = app_group::GetGroupUserDefaults();
  151. NSInteger numberOfDisplay =
  152. [sharedDefaults integerForKey:app_group::kContentExtensionDisplayCount];
  153. [sharedDefaults setInteger:numberOfDisplay + 1
  154. forKey:app_group::kContentExtensionDisplayCount];
  155. }
  156. - (void)openURL:(NSURL*)URL {
  157. AppGroupCommand* command = [[AppGroupCommand alloc]
  158. initWithSourceApp:app_group::kOpenCommandSourceContentExtension
  159. URLOpenerBlock:^(NSURL* openURL) {
  160. [self.extensionContext openURL:openURL completionHandler:nil];
  161. }];
  162. [command prepareToOpenURL:URL];
  163. [command executeInApp];
  164. }
  165. @end