host_view_controller.mm 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  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. #if !defined(__has_feature) || !__has_feature(objc_arc)
  5. #error "This file requires ARC support."
  6. #endif
  7. #import "remoting/ios/app/host_view_controller.h"
  8. #include <memory>
  9. #import <MaterialComponents/MaterialButtons.h>
  10. #include "base/logging.h"
  11. #include "base/strings/sys_string_conversions.h"
  12. #include "remoting/base/string_resources.h"
  13. #include "remoting/client/chromoting_client_runtime.h"
  14. #include "remoting/client/gesture_interpreter.h"
  15. #include "remoting/client/input/keyboard_interpreter.h"
  16. #import "remoting/ios/app/help_and_feedback.h"
  17. #import "remoting/ios/app/remoting_theme.h"
  18. #import "remoting/ios/app/settings/remoting_settings_view_controller.h"
  19. #import "remoting/ios/app/view_utils.h"
  20. #import "remoting/ios/client_gestures.h"
  21. #import "remoting/ios/client_keyboard.h"
  22. #import "remoting/ios/display/eagl_view.h"
  23. #import "remoting/ios/domain/host_info.h"
  24. #import "remoting/ios/domain/host_settings.h"
  25. #import "remoting/ios/mdc/MDCActionImageView.h"
  26. #import "remoting/ios/persistence/remoting_preferences.h"
  27. #import "remoting/ios/session/remoting_client.h"
  28. #include "ui/base/l10n/l10n_util.h"
  29. static const CGFloat kFabInset = 15.f;
  30. static const CGFloat kKeyboardAnimationTime = 0.3;
  31. static const CGFloat kMoveFABAnimationTime = 0.3;
  32. static NSString* const kFeedbackContext = @"InSessionFeedbackContext";
  33. @interface HostViewController ()<ClientKeyboardDelegate,
  34. ClientGesturesDelegate,
  35. RemotingSettingsViewControllerDelegate> {
  36. RemotingClient* _client;
  37. MDCActionImageView* _actionImageView;
  38. MDCFloatingButton* _floatingButton;
  39. ClientGestures* _clientGestures;
  40. ClientKeyboard* _clientKeyboard;
  41. CGSize _keyboardSize;
  42. HostSettings* _settings;
  43. // Used to blur the content when the app enters background.
  44. UIView* _blurView;
  45. // Only change this by calling setFabIsRight:.
  46. BOOL _fabIsRight;
  47. NSArray<NSLayoutConstraint*>* _fabLeftConstraints;
  48. NSArray<NSLayoutConstraint*>* _fabRightConstraints;
  49. // When set to true, ClientKeyboard will immediately resign first responder
  50. // after it becomes first responder.
  51. BOOL _blocksKeyboard;
  52. NSLayoutConstraint* _keyboardHeightConstraint;
  53. // Subview of self.view. Adjusted frame for safe area.
  54. EAGLView* _hostView;
  55. // A placeholder view for anchoring views and calculating visible area.
  56. UIView* _keyboardPlaceholderView;
  57. // A display link for animating keyboard height change. Use the paused
  58. // property to start or stop the animation.
  59. CADisplayLink* _keyboardHeightAnimationLink;
  60. }
  61. @end
  62. @implementation HostViewController
  63. - (id)initWithClient:(RemotingClient*)client {
  64. self = [super init];
  65. if (self) {
  66. _client = client;
  67. _keyboardSize = CGSizeZero;
  68. _blocksKeyboard = NO;
  69. _settings =
  70. [[RemotingPreferences instance] settingsForHost:client.hostInfo.hostId];
  71. BOOL fabIsRight =
  72. [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:
  73. self.view.semanticContentAttribute] ==
  74. UIUserInterfaceLayoutDirectionLeftToRight;
  75. [self setFabIsRight:fabIsRight shouldLayout:NO];
  76. }
  77. return self;
  78. }
  79. #pragma mark - UIViewController
  80. - (void)viewDidLoad {
  81. [super viewDidLoad];
  82. _hostView = [[EAGLView alloc] initWithFrame:CGRectZero];
  83. _hostView.translatesAutoresizingMaskIntoConstraints = NO;
  84. // Allow the host view to handle raw gestures.
  85. _hostView.isAccessibilityElement = YES;
  86. _hostView.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction;
  87. [self.view addSubview:_hostView];
  88. [NSLayoutConstraint activateConstraints:@[
  89. [_hostView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
  90. [_hostView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
  91. [_hostView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  92. [_hostView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
  93. ]];
  94. _hostView.displayTaskRunner =
  95. remoting::ChromotingClientRuntime::GetInstance()->display_task_runner();
  96. _keyboardPlaceholderView = [[UIView alloc] initWithFrame:CGRectZero];
  97. _keyboardPlaceholderView.translatesAutoresizingMaskIntoConstraints = NO;
  98. [_hostView addSubview:_keyboardPlaceholderView];
  99. [NSLayoutConstraint activateConstraints:@[
  100. [_keyboardPlaceholderView.leadingAnchor
  101. constraintEqualToAnchor:self.view.leadingAnchor],
  102. [_keyboardPlaceholderView.trailingAnchor
  103. constraintEqualToAnchor:self.view.trailingAnchor],
  104. [_keyboardPlaceholderView.bottomAnchor
  105. constraintEqualToAnchor:self.view.bottomAnchor],
  106. ]];
  107. _floatingButton =
  108. [MDCFloatingButton floatingButtonWithShape:MDCFloatingButtonShapeMini];
  109. // Note(nicholss): Setting title to " " because the FAB requires the title
  110. // or image to be set but we are using the rotating image instead. Until this
  111. // is directly supported by the FAB, a space for the title is a work-around.
  112. [_floatingButton setTitle:@" " forState:UIControlStateNormal];
  113. [_floatingButton setBackgroundColor:RemotingTheme.buttonBackgroundColor
  114. forState:UIControlStateNormal];
  115. [_floatingButton addTarget:self
  116. action:@selector(didTap:)
  117. forControlEvents:UIControlEventTouchUpInside];
  118. [_floatingButton sizeToFit];
  119. _floatingButton.translatesAutoresizingMaskIntoConstraints = NO;
  120. _actionImageView =
  121. [[MDCActionImageView alloc] initWithFrame:_floatingButton.bounds
  122. primaryImage:RemotingTheme.menuIcon
  123. activeImage:RemotingTheme.closeIcon];
  124. [_floatingButton addSubview:_actionImageView];
  125. // TODO(yuweih): The accessibility label should be changed to "Close" when
  126. // the FAB is open.
  127. _floatingButton.accessibilityLabel =
  128. l10n_util::GetNSString(IDS_ACTIONBAR_MENU);
  129. [self.view addSubview:_floatingButton];
  130. [self applyInputMode];
  131. _clientKeyboard = [[ClientKeyboard alloc] init];
  132. _clientKeyboard.delegate = self;
  133. [_hostView addSubview:_clientKeyboard];
  134. _fabLeftConstraints = @[ [_floatingButton.leftAnchor
  135. constraintEqualToAnchor:_hostView.leftAnchor
  136. constant:kFabInset] ];
  137. _fabRightConstraints = @[ [_floatingButton.rightAnchor
  138. constraintEqualToAnchor:_hostView.rightAnchor
  139. constant:-kFabInset] ];
  140. [_floatingButton.bottomAnchor
  141. constraintEqualToAnchor:_keyboardPlaceholderView.topAnchor
  142. constant:-kFabInset]
  143. .active = YES;
  144. [self setKeyboardSize:CGSizeZero needsLayout:NO];
  145. remoting::PostDelayedAccessibilityNotification(
  146. l10n_util::GetNSString(IDS_HOST_CONNECTED_ANNOUNCEMENT));
  147. }
  148. - (void)viewDidUnload {
  149. [super viewDidUnload];
  150. // TODO(nicholss): There needs to be a hook to tell the client we are done.
  151. [_hostView stop];
  152. _clientGestures = nil;
  153. _client = nil;
  154. }
  155. - (BOOL)prefersStatusBarHidden {
  156. return YES;
  157. }
  158. - (BOOL)prefersHomeIndicatorAutoHidden {
  159. // Allow home indicator to timeout so that user can see desktop on the bottom
  160. // of the screen.
  161. return YES;
  162. }
  163. - (void)viewDidAppear:(BOOL)animated {
  164. [super viewDidAppear:animated];
  165. [_client.displayHandler createRendererContext:_hostView];
  166. // |_clientKeyboard| should always be the first responder even when the soft
  167. // keyboard is not visible, so that input from physical keyboard can still be
  168. // captured.
  169. [_clientKeyboard becomeFirstResponder];
  170. }
  171. - (void)viewWillAppear:(BOOL)animated {
  172. [super viewWillAppear:animated];
  173. if (!_clientGestures) {
  174. _clientGestures =
  175. [[ClientGestures alloc] initWithView:_hostView client:_client];
  176. _clientGestures.delegate = self;
  177. }
  178. [[NSNotificationCenter defaultCenter]
  179. addObserver:self
  180. selector:@selector(keyboardWillShow:)
  181. name:UIKeyboardWillShowNotification
  182. object:nil];
  183. [[NSNotificationCenter defaultCenter]
  184. addObserver:self
  185. selector:@selector(keyboardWillHide:)
  186. name:UIKeyboardWillHideNotification
  187. object:nil];
  188. [NSNotificationCenter.defaultCenter
  189. addObserver:self
  190. selector:@selector(applicationDidBecomeActive:)
  191. name:UIApplicationDidBecomeActiveNotification
  192. object:nil];
  193. [NSNotificationCenter.defaultCenter
  194. addObserver:self
  195. selector:@selector(applicationWillResignActive:)
  196. name:UIApplicationWillResignActiveNotification
  197. object:nil];
  198. // If the host view is presented when the app is inactive, synthesize an
  199. // initial UIApplicationWillResignActiveNotification event.
  200. if (UIApplication.sharedApplication.applicationState !=
  201. UIApplicationStateActive) {
  202. [self applicationWillResignActive:UIApplication.sharedApplication];
  203. }
  204. _keyboardHeightAnimationLink =
  205. [CADisplayLink displayLinkWithTarget:self
  206. selector:@selector(animateKeyboardHeight:)];
  207. _keyboardHeightAnimationLink.paused = YES;
  208. [_keyboardHeightAnimationLink addToRunLoop:NSRunLoop.currentRunLoop
  209. forMode:NSDefaultRunLoopMode];
  210. }
  211. - (void)viewWillDisappear:(BOOL)animated {
  212. [super viewWillDisappear:animated];
  213. [[RemotingPreferences instance] setSettings:_settings
  214. forHost:_client.hostInfo.hostId];
  215. [[NSNotificationCenter defaultCenter] removeObserver:self];
  216. _keyboardHeightAnimationLink.paused = YES;
  217. [_keyboardHeightAnimationLink invalidate];
  218. }
  219. - (void)viewDidLayoutSubviews {
  220. [super viewDidLayoutSubviews];
  221. [self updateViewportSafeInsets];
  222. // Pass the actual size of the view to the renderer.
  223. [_client.displayHandler setSurfaceSize:_hostView.bounds];
  224. _client.gestureInterpreter->OnSurfaceSizeChanged(
  225. _hostView.bounds.size.width, _hostView.bounds.size.height);
  226. // Start the safe insets animation.
  227. _keyboardHeightAnimationLink.paused = NO;
  228. [self resizeHostToFitIfNeeded];
  229. }
  230. #pragma mark - Keyboard Notifications
  231. - (void)keyboardWillShow:(NSNotification*)notification {
  232. // Note that this won't be called in split keyboard mode.
  233. // keyboardWillShow may be called with a wrong keyboard size when the physical
  234. // keyboard is plugged in while the soft keyboard is hidden. This is
  235. // potentially an OS bug. `!_clientKeyboard.showsSoftKeyboard` works around
  236. // it.
  237. if (!_clientKeyboard.showsSoftKeyboard) {
  238. return;
  239. }
  240. if (_blocksKeyboard) {
  241. // This is to make sure the keyboard is removed from the responder chain.
  242. [_clientKeyboard removeFromSuperview];
  243. [self.view addSubview:_clientKeyboard];
  244. _clientKeyboard.showsSoftKeyboard = NO;
  245. [_clientKeyboard becomeFirstResponder];
  246. return;
  247. }
  248. // On iOS 10 the keyboard might be partially shown, i.e. part of the keyboard
  249. // is below the screen.
  250. CGRect keyboardRect = [[[notification userInfo]
  251. objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
  252. CGSize visibleKeyboardSize =
  253. CGRectIntersection(self.view.bounds, keyboardRect).size;
  254. [self setKeyboardSize:visibleKeyboardSize needsLayout:YES];
  255. }
  256. - (void)keyboardWillHide:(NSNotification*)notification {
  257. if (!_clientKeyboard.isFirstResponder) {
  258. return;
  259. }
  260. [self setKeyboardSize:CGSizeZero needsLayout:YES];
  261. }
  262. #pragma mark - ClientKeyboardDelegate
  263. - (void)clientKeyboardShouldSend:(NSString*)text {
  264. _client.keyboardInterpreter->HandleTextEvent(base::SysNSStringToUTF8(text),
  265. 0);
  266. }
  267. - (void)clientKeyboardShouldSendKey:(const remoting::KeypressInfo&)key {
  268. _client.keyboardInterpreter->HandleKeypressEvent(key);
  269. }
  270. - (void)clientKeyboardShouldDelete {
  271. _client.keyboardInterpreter->HandleDeleteEvent(0);
  272. }
  273. #pragma mark - ClientGesturesDelegate
  274. - (void)keyboardShouldShow {
  275. _clientKeyboard.showsSoftKeyboard = YES;
  276. }
  277. - (void)keyboardShouldHide {
  278. _clientKeyboard.showsSoftKeyboard = NO;
  279. }
  280. - (void)menuShouldShow {
  281. [self didTap:_floatingButton];
  282. }
  283. #pragma mark - RemotingSettingsViewControllerDelegate
  284. - (void)setResizeToFit:(BOOL)resizeToFit {
  285. _settings.shouldResizeHostToFit = resizeToFit;
  286. [self resizeHostToFitIfNeeded];
  287. }
  288. - (void)useDirectInputMode {
  289. _settings.inputMode = ClientInputModeDirect;
  290. [self applyInputMode];
  291. }
  292. - (void)useTrackpadInputMode {
  293. _settings.inputMode = ClientInputModeTrackpad;
  294. [self applyInputMode];
  295. }
  296. - (void)sendCtrAltDel {
  297. _client.keyboardInterpreter->HandleCtrlAltDeleteEvent();
  298. }
  299. - (void)sendPrintScreen {
  300. _client.keyboardInterpreter->HandlePrintScreenEvent();
  301. }
  302. - (void)moveFAB {
  303. [self setFabIsRight:!_fabIsRight shouldLayout:YES];
  304. }
  305. - (void)sendFeedback {
  306. [_client createFeedbackDataWithCallback:^(
  307. const remoting::FeedbackData& data) {
  308. [HelpAndFeedback.instance presentFeedbackFlowWithContext:kFeedbackContext
  309. feedbackData:data];
  310. }];
  311. }
  312. #pragma mark - Private
  313. - (void)setFabIsRight:(BOOL)fabIsRight shouldLayout:(BOOL)shouldLayout {
  314. _fabIsRight = fabIsRight;
  315. [NSLayoutConstraint deactivateConstraints:_fabRightConstraints];
  316. [NSLayoutConstraint deactivateConstraints:_fabLeftConstraints];
  317. if (_fabIsRight) {
  318. [NSLayoutConstraint activateConstraints:_fabRightConstraints];
  319. } else {
  320. [NSLayoutConstraint activateConstraints:_fabLeftConstraints];
  321. }
  322. if (shouldLayout) {
  323. [UIView animateWithDuration:kMoveFABAnimationTime
  324. animations:^{
  325. [self.view layoutIfNeeded];
  326. }];
  327. }
  328. }
  329. - (void)resizeHostToFitIfNeeded {
  330. if (_settings.shouldResizeHostToFit) {
  331. UIEdgeInsets safeInsets = remoting::SafeAreaInsetsForView(_hostView);
  332. CGRect safeRect = UIEdgeInsetsInsetRect(_hostView.frame, safeInsets);
  333. [_client setHostResolution:safeRect.size
  334. scale:_hostView.contentScaleFactor];
  335. }
  336. }
  337. - (void)animateKeyboardHeight:(CADisplayLink*)link {
  338. // The method is called when the keyboard animation is in-progress. It
  339. // calculates the intermediate visible area size during the animation and
  340. // passes it to DesktopViewport.
  341. // This method is called in sync with the refresh cycle, otherwise the frame
  342. // rate will drop for some reason. Note that the actual rendering process is
  343. // done on the display thread asynchronously, so unfortunately the animation
  344. // will not be perfectly synchronized with the keyboard animation.
  345. [self updateViewportSafeInsets];
  346. CALayer* kbPlaceholderLayer =
  347. [_keyboardPlaceholderView.layer presentationLayer];
  348. CGFloat currentKeyboardHeight = kbPlaceholderLayer.frame.size.height;
  349. CGFloat targetKeyboardHeight = _keyboardPlaceholderView.frame.size.height;
  350. if (currentKeyboardHeight == targetKeyboardHeight) {
  351. // Animation is done.
  352. _keyboardHeightAnimationLink.paused = YES;
  353. }
  354. }
  355. - (void)updateViewportSafeInsets {
  356. // The viewport safe insets consist of area that is (partially) obstructed by
  357. // the notch and the soft keyboard.
  358. CALayer* kbPlaceholderLayer =
  359. [_keyboardPlaceholderView.layer presentationLayer];
  360. CGRect viewKeyboardIntersection =
  361. CGRectIntersection(kbPlaceholderLayer.frame, _hostView.frame);
  362. UIEdgeInsets safeInsets = remoting::SafeAreaInsetsForView(_hostView);
  363. safeInsets.bottom =
  364. std::max(safeInsets.bottom, viewKeyboardIntersection.size.height);
  365. _client.gestureInterpreter->OnSafeInsetsChanged(
  366. safeInsets.left, safeInsets.top, safeInsets.right, safeInsets.bottom);
  367. }
  368. - (void)disconnectFromHost {
  369. [_client disconnectFromHost];
  370. [_keyboardHeightAnimationLink invalidate];
  371. _keyboardHeightAnimationLink = nil;
  372. }
  373. - (void)applyInputMode {
  374. switch (_settings.inputMode) {
  375. case ClientInputModeTrackpad:
  376. _client.gestureInterpreter->SetInputMode(
  377. remoting::GestureInterpreter::TRACKPAD_INPUT_MODE);
  378. break;
  379. case ClientInputModeDirect: // Fall-through.
  380. default:
  381. _client.gestureInterpreter->SetInputMode(
  382. remoting::GestureInterpreter::DIRECT_INPUT_MODE);
  383. }
  384. }
  385. // TODO(yuweih): This method is badly named. Should be changed to
  386. // "didTapShowMenu".
  387. - (void)didTap:(id)sender {
  388. // TODO(nicholss): The FAB is being used to launch an alert window with
  389. // more options. This is not ideal but it gets us an easy way to make a
  390. // modal window option selector. Replace this with a real menu later.
  391. // ClientKeyboard may gain first responder immediately after the alert is
  392. // dismissed. This will cause weird show-then-hide animation when hiding
  393. // keyboard on iPhone (iPad is unaffected since it shows the alert as popup).
  394. // The fix is to remove ClientKeyboard from the responder chain in
  395. // keyboardWillShow and manually show the keyboard again only when needed.
  396. UIAlertController* alert = [UIAlertController
  397. alertControllerWithTitle:nil
  398. message:nil
  399. preferredStyle:UIAlertControllerStyleActionSheet];
  400. __weak HostViewController* weakSelf = self;
  401. __weak ClientKeyboard* weakClientKeyboard = _clientKeyboard;
  402. if (_clientKeyboard.showsSoftKeyboard) {
  403. [self addActionToAlert:alert
  404. title:IDS_HIDE_KEYBOARD
  405. style:UIAlertActionStyleDefault
  406. restoresKeyboard:NO
  407. handler:^{
  408. weakClientKeyboard.showsSoftKeyboard = NO;
  409. }];
  410. } else {
  411. [self addActionToAlert:alert
  412. title:IDS_SHOW_KEYBOARD
  413. handler:^{
  414. weakClientKeyboard.showsSoftKeyboard = YES;
  415. }];
  416. }
  417. remoting::GestureInterpreter::InputMode currentInputMode =
  418. _client.gestureInterpreter->GetInputMode();
  419. int switchInputModeTitle =
  420. currentInputMode == remoting::GestureInterpreter::DIRECT_INPUT_MODE
  421. ? IDS_SELECT_TRACKPAD_MODE
  422. : IDS_SELECT_TOUCH_MODE;
  423. void (^switchInputModeHandler)() = ^{
  424. switch (currentInputMode) {
  425. case remoting::GestureInterpreter::DIRECT_INPUT_MODE:
  426. [self useTrackpadInputMode];
  427. break;
  428. case remoting::GestureInterpreter::TRACKPAD_INPUT_MODE: // Fall-through.
  429. default:
  430. [self useDirectInputMode];
  431. break;
  432. }
  433. };
  434. [self addActionToAlert:alert
  435. title:switchInputModeTitle
  436. handler:switchInputModeHandler];
  437. void (^disconnectHandler)() = ^{
  438. HostViewController* strongSelf = weakSelf;
  439. if (strongSelf) {
  440. [strongSelf disconnectFromHost];
  441. [strongSelf.navigationController popToRootViewControllerAnimated:YES];
  442. }
  443. };
  444. [self addActionToAlert:alert
  445. title:IDS_DISCONNECT_MYSELF_BUTTON
  446. style:UIAlertActionStyleDefault
  447. restoresKeyboard:NO
  448. handler:disconnectHandler];
  449. void (^settingsHandler)() = ^{
  450. HostViewController* strongSelf = weakSelf;
  451. if (strongSelf) {
  452. RemotingSettingsViewController* settingsViewController =
  453. [[RemotingSettingsViewController alloc] init];
  454. settingsViewController.delegate = strongSelf;
  455. settingsViewController.inputMode = currentInputMode;
  456. settingsViewController.shouldResizeHostToFit =
  457. strongSelf->_settings.shouldResizeHostToFit;
  458. UINavigationController* navController = [[UINavigationController alloc]
  459. initWithRootViewController:settingsViewController];
  460. [strongSelf presentViewController:navController
  461. animated:YES
  462. completion:nil];
  463. }
  464. };
  465. // Don't restore keyboard since the settings view will be show immediately.
  466. [self addActionToAlert:alert
  467. title:IDS_SETTINGS_BUTTON
  468. style:UIAlertActionStyleDefault
  469. restoresKeyboard:NO
  470. handler:settingsHandler];
  471. [self addActionToAlert:alert
  472. title:(_fabIsRight) ? IDS_MOVE_FAB_LEFT_BUTTON
  473. : IDS_MOVE_FAB_RIGHT_BUTTON
  474. handler:^{
  475. [weakSelf moveFAB];
  476. }];
  477. __weak UIAlertController* weakAlert = alert;
  478. void (^cancelHandler)() = ^{
  479. [weakAlert dismissViewControllerAnimated:YES completion:nil];
  480. };
  481. [self addActionToAlert:alert
  482. title:IDS_CANCEL
  483. style:UIAlertActionStyleCancel
  484. restoresKeyboard:YES
  485. handler:cancelHandler];
  486. alert.popoverPresentationController.sourceView = _hostView;
  487. // Target the alert menu at the top middle of the FAB.
  488. alert.popoverPresentationController.sourceRect = CGRectMake(
  489. _floatingButton.center.x, _floatingButton.frame.origin.y, 1.0, 1.0);
  490. alert.popoverPresentationController.permittedArrowDirections =
  491. UIPopoverArrowDirectionDown;
  492. [self presentViewController:alert animated:YES completion:nil];
  493. // Prevent keyboard from showing between (alert is shown, action is executed).
  494. _blocksKeyboard = YES;
  495. [_actionImageView setActive:YES animated:YES];
  496. }
  497. // Adds an action to the alert. And restores the states for you.
  498. // restoresKeyboard:
  499. // Set to YES to show the keyboard if it was previously shown. Do not assume
  500. // the keyboard will always be hidden when the alert view is shown.
  501. - (void)addActionToAlert:(UIAlertController*)alert
  502. title:(int)titleMessageId
  503. style:(UIAlertActionStyle)style
  504. restoresKeyboard:(BOOL)restoresKeyboard
  505. handler:(void (^)())handler {
  506. BOOL isKeyboardActive = _clientKeyboard.showsSoftKeyboard;
  507. [alert addAction:[UIAlertAction
  508. actionWithTitle:l10n_util::GetNSString(titleMessageId)
  509. style:style
  510. handler:^(UIAlertAction*) {
  511. self->_blocksKeyboard = NO;
  512. if (isKeyboardActive && restoresKeyboard) {
  513. self->_clientKeyboard.showsSoftKeyboard =
  514. YES;
  515. }
  516. if (handler) {
  517. handler();
  518. }
  519. [self->_actionImageView setActive:NO
  520. animated:YES];
  521. }]];
  522. }
  523. // Shorter version of addActionToAlert with default action style and
  524. // restoresKeyboard == YES.
  525. - (void)addActionToAlert:(UIAlertController*)alert
  526. title:(int)titleMessageId
  527. handler:(void (^)())handler {
  528. [self addActionToAlert:alert
  529. title:titleMessageId
  530. style:UIAlertActionStyleDefault
  531. restoresKeyboard:YES
  532. handler:handler];
  533. }
  534. - (void)setKeyboardSize:(CGSize)keyboardSize needsLayout:(BOOL)needsLayout {
  535. _keyboardSize = keyboardSize;
  536. if (_keyboardHeightConstraint) {
  537. _keyboardHeightConstraint.active = NO;
  538. }
  539. _keyboardHeightConstraint = [_keyboardPlaceholderView.heightAnchor
  540. constraintEqualToConstant:keyboardSize.height];
  541. _keyboardHeightConstraint.active = YES;
  542. if (needsLayout) {
  543. [UIView animateWithDuration:kKeyboardAnimationTime
  544. animations:^{
  545. [self.view layoutIfNeeded];
  546. }];
  547. }
  548. }
  549. - (void)applicationDidBecomeActive:(UIApplication*)application {
  550. if (!_blurView) {
  551. LOG(DFATAL) << "Blur view does not exist.";
  552. return;
  553. }
  554. [_client.displayHandler createRendererContext:_hostView];
  555. [_client setVideoChannelEnabled:YES];
  556. [_blurView removeFromSuperview];
  557. _blurView = nil;
  558. }
  559. - (void)applicationWillResignActive:(UIApplication*)application {
  560. if (_blurView) {
  561. LOG(DFATAL) << "Blur view already exists.";
  562. return;
  563. }
  564. UIBlurEffect* effect =
  565. [UIBlurEffect effectWithStyle:UIBlurEffectStyleRegular];
  566. _blurView = [[UIVisualEffectView alloc] initWithEffect:effect];
  567. _blurView.translatesAutoresizingMaskIntoConstraints = NO;
  568. [self.view insertSubview:_blurView aboveSubview:_hostView];
  569. [NSLayoutConstraint activateConstraints:@[
  570. [_blurView.leadingAnchor constraintEqualToAnchor:_hostView.leadingAnchor],
  571. [_blurView.trailingAnchor constraintEqualToAnchor:_hostView.trailingAnchor],
  572. [_blurView.topAnchor constraintEqualToAnchor:_hostView.topAnchor],
  573. [_blurView.bottomAnchor constraintEqualToAnchor:_hostView.bottomAnchor],
  574. ]];
  575. [_client setVideoChannelEnabled:NO];
  576. [_client.displayHandler destroyRendererContext];
  577. }
  578. @end