FlutterViewController.mm 31.9 KB
Newer Older
1
// Copyright 2016 The Chromium Authors. All rights reserved.
C
Chinmay Garde 已提交
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
C
Chinmay Garde 已提交
6

A
Adam Barth 已提交
7 8 9
#include <memory>

#include "flutter/common/threads.h"
10
#include "flutter/fml/platform/darwin/platform_version.h"
11 12
#include "flutter/fml/platform/darwin/scoped_block.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
13
#include "flutter/shell/platform/darwin/common/buffer_conversions.h"
14
#include "flutter/shell/platform/darwin/common/platform_mac.h"
15
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
16
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
17
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
18 19
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
20
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_main_ios.h"
21
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.h"
22
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
23 24
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/time/time_delta.h"
25

26 27
namespace {

28
typedef void (^PlatformMessageResponseCallback)(NSData*);
29 30 31 32 33 34

class PlatformMessageResponseDarwin : public blink::PlatformMessageResponse {
  FRIEND_MAKE_REF_COUNTED(PlatformMessageResponseDarwin);

 public:
  void Complete(std::vector<uint8_t> data) override {
35
    fxl::RefPtr<PlatformMessageResponseDarwin> self(this);
36
    blink::Threads::Platform()->PostTask(
37
        fxl::MakeCopyable([ self, data = std::move(data) ]() mutable {
38
          self->callback_.get()(shell::GetNSDataFromVector(data));
39 40 41
        }));
  }

42
  void CompleteEmpty() override {
43
    fxl::RefPtr<PlatformMessageResponseDarwin> self(this);
44
    blink::Threads::Platform()->PostTask(
45
        fxl::MakeCopyable([self]() mutable { self->callback_.get()(nil); }));
46
  }
47 48

 private:
49
  explicit PlatformMessageResponseDarwin(PlatformMessageResponseCallback callback)
50
      : callback_(callback, fml::OwnershipPolicy::Retain) {}
51

52
  fml::ScopedBlock<PlatformMessageResponseCallback> callback_;
53 54 55 56
};

}  // namespace

57
@interface FlutterViewController ()<UIAlertViewDelegate, FlutterTextInputDelegate>
58 59
@end

60
@implementation FlutterViewController {
61
  fml::scoped_nsprotocol<FlutterDartProject*> _dartProject;
62
  UIInterfaceOrientationMask _orientationPreferences;
63
  UIStatusBarStyle _statusBarStyle;
64
  blink::ViewportMetrics _viewportMetrics;
65
  shell::TouchMapper _touchMapper;
M
Michael Goderbauer 已提交
66
  std::shared_ptr<shell::PlatformViewIOS> _platformView;
67 68 69 70 71 72
  fml::scoped_nsprotocol<FlutterPlatformPlugin*> _platformPlugin;
  fml::scoped_nsprotocol<FlutterTextInputPlugin*> _textInputPlugin;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _localizationChannel;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _navigationChannel;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _platformChannel;
  fml::scoped_nsprotocol<FlutterMethodChannel*> _textInputChannel;
73 74
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _lifecycleChannel;
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _systemChannel;
75
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _settingsChannel;
76
  fml::scoped_nsprotocol<UIView*> _launchView;
77 78 79
  bool _platformSupportsTouchTypes;
  bool _platformSupportsTouchPressure;
  bool _platformSupportsTouchOrientationAndTilt;
80
  bool _platformSupportsSafeAreaInsets;
81
  BOOL _initialized;
82
  BOOL _connected;
83 84
}

85 86 87 88 89 90
+ (void)initialize {
  if (self == [FlutterViewController class]) {
    shell::FlutterMain();
  }
}

91 92
#pragma mark - Manage and override all designated initializers

93 94 95 96
- (instancetype)initWithProject:(FlutterDartProject*)project
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
97

98
  if (self) {
99
    if (project == nil)
100
      _dartProject.reset([[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]);
101 102
    else
      _dartProject.reset([project retain]);
103 104

    [self performCommonViewControllerInitialization];
105
  }
106

107
  return self;
C
Chinmay Garde 已提交
108 109
}

110
- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
111
  return [self initWithProject:nil nibName:nil bundle:nil];
C
Chinmay Garde 已提交
112 113
}

114
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
115
  return [self initWithProject:nil nibName:nil bundle:nil];
116 117
}

118
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
119

120
- (void)performCommonViewControllerInitialization {
121
  if (_initialized)
122
    return;
123

124
  _initialized = YES;
C
Chinmay Garde 已提交
125

126 127 128
  _platformSupportsTouchTypes = fml::IsPlatformVersionAtLeast(9);
  _platformSupportsTouchPressure = fml::IsPlatformVersionAtLeast(9);
  _platformSupportsTouchOrientationAndTilt = fml::IsPlatformVersionAtLeast(9, 1);
129
  _platformSupportsSafeAreaInsets = fml::IsPlatformVersionAtLeast(11, 0);
130

131
  _orientationPreferences = UIInterfaceOrientationMaskAll;
132
  _statusBarStyle = UIStatusBarStyleDefault;
133 134
  _platformView = std::make_shared<shell::PlatformViewIOS>(
      reinterpret_cast<CAEAGLLayer*>(self.view.layer), self);
135 136 137 138 139 140 141

  _platformView->Attach(
      // First frame callback.
      [self]() {
        TRACE_EVENT0("flutter", "First Frame");
        if (_launchView) {
          [UIView animateWithDuration:0.2
142 143 144 145 146 147 148
              animations:^{
                _launchView.get().alpha = 0;
              }
              completion:^(BOOL finished) {
                [_launchView.get() removeFromSuperview];
                _launchView.reset();
              }];
149 150
        }
      });
151
  _platformView->SetupResourceContextOnIOThread();
C
Chinmay Garde 已提交
152

153
  _localizationChannel.reset([[FlutterMethodChannel alloc]
154
         initWithName:@"flutter/localization"
155
      binaryMessenger:self
156
                codec:[FlutterJSONMethodCodec sharedInstance]]);
157 158

  _navigationChannel.reset([[FlutterMethodChannel alloc]
159
         initWithName:@"flutter/navigation"
160
      binaryMessenger:self
161
                codec:[FlutterJSONMethodCodec sharedInstance]]);
162 163

  _platformChannel.reset([[FlutterMethodChannel alloc]
164
         initWithName:@"flutter/platform"
165
      binaryMessenger:self
166
                codec:[FlutterJSONMethodCodec sharedInstance]]);
167 168

  _textInputChannel.reset([[FlutterMethodChannel alloc]
169
         initWithName:@"flutter/textinput"
170
      binaryMessenger:self
171
                codec:[FlutterJSONMethodCodec sharedInstance]]);
172

173
  _lifecycleChannel.reset([[FlutterBasicMessageChannel alloc]
174
         initWithName:@"flutter/lifecycle"
175
      binaryMessenger:self
176
                codec:[FlutterStringCodec sharedInstance]]);
177

178
  _systemChannel.reset([[FlutterBasicMessageChannel alloc]
179
         initWithName:@"flutter/system"
180
      binaryMessenger:self
181
                codec:[FlutterJSONMessageCodec sharedInstance]]);
182

183 184 185 186 187
  _settingsChannel.reset([[FlutterBasicMessageChannel alloc]
         initWithName:@"flutter/settings"
      binaryMessenger:self
                codec:[FlutterJSONMessageCodec sharedInstance]]);

188
  _platformPlugin.reset([[FlutterPlatformPlugin alloc] init]);
189 190 191
  [_platformChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [_platformPlugin.get() handleMethodCall:call result:result];
  }];
192

193 194
  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
  _textInputPlugin.get().textInputDelegate = self;
195 196 197
  [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [_textInputPlugin.get() handleMethodCall:call result:result];
  }];
198

199
  [self setupNotificationCenterObservers];
C
Chinmay Garde 已提交
200 201
}

202 203 204 205
- (void)setupNotificationCenterObservers {
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onOrientationPreferencesUpdated:)
A
Adam Barth 已提交
206
                 name:@(shell::kOrientationUpdateNotificationName)
207 208
               object:nil];

209 210
  [center addObserver:self
             selector:@selector(onPreferredStatusBarStyleUpdated:)
A
Adam Barth 已提交
211
                 name:@(shell::kOverlayStyleUpdateNotificationName)
212 213
               object:nil];

214 215 216 217 218 219 220 221 222 223
  [center addObserver:self
             selector:@selector(applicationBecameActive:)
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationWillResignActive:)
                 name:UIApplicationWillResignActiveNotification
               object:nil];

224 225 226 227 228 229 230 231 232 233
  [center addObserver:self
             selector:@selector(applicationDidEnterBackground:)
                 name:UIApplicationDidEnterBackgroundNotification
               object:nil];

  [center addObserver:self
             selector:@selector(applicationWillEnterForeground:)
                 name:UIApplicationWillEnterForegroundNotification
               object:nil];

234
  [center addObserver:self
235 236
             selector:@selector(keyboardWillChangeFrame:)
                 name:UIKeyboardWillChangeFrameNotification
237 238 239 240 241 242 243 244 245 246 247
               object:nil];

  [center addObserver:self
             selector:@selector(keyboardWillBeHidden:)
                 name:UIKeyboardWillHideNotification
               object:nil];

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];
248 249 250 251 252

  [center addObserver:self
             selector:@selector(onVoiceOverChanged:)
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];
253 254 255 256 257

  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];
258 259 260 261 262

  [center addObserver:self
             selector:@selector(onUserSettingsChanged:)
                 name:UIContentSizeCategoryDidChangeNotification
               object:nil];
C
Chinmay Garde 已提交
263 264
}

265
- (void)setInitialRoute:(NSString*)route {
266
  [_navigationChannel.get() invokeMethod:@"setInitialRoute" arguments:route];
267
}
268
#pragma mark - Initializing the engine
269

270
- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
271 272 273
  exit(0);
}

274
- (void)connectToEngineAndLoad {
275 276 277 278
  if (_connected)
    return;
  _connected = YES;

C
Chinmay Garde 已提交
279
  TRACE_EVENT0("flutter", "connectToEngineAndLoad");
280

281
  // We ask the VM to check what it supports.
282
  const enum VMType type = Dart_IsPrecompiledRuntime() ? VMTypePrecompilation : VMTypeInterpreter;
283

A
Adam Barth 已提交
284
  [_dartProject launchInEngine:&_platformView->engine()
285 286 287
                embedderVMType:type
                        result:^(BOOL success, NSString* message) {
                          if (!success) {
288 289 290 291 292
                            UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Launch Error"
                                                                            message:message
                                                                           delegate:self
                                                                  cancelButtonTitle:@"OK"
                                                                  otherButtonTitles:nil];
293 294 295 296
                            [alert show];
                            [alert release];
                          }
                        }];
297 298 299 300 301
}

#pragma mark - Loading the view

- (void)loadView {
302
  FlutterView* view = [[FlutterView alloc] init];
303

304
  self.view = view;
305
  self.view.multipleTouchEnabled = YES;
306
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
307

308
  [view release];
309 310 311 312 313 314 315

  // Show the launch screen view again on top of the FlutterView if available.
  // This launch screen view will be removed once the first Flutter frame is rendered.
  NSString* launchStoryboardName =
      [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
  if (launchStoryboardName && !self.isBeingPresented && !self.isMovingToParentViewController) {
    UIViewController* launchViewController =
316 317
        [[UIStoryboard storyboardWithName:launchStoryboardName bundle:nil]
            instantiateInitialViewController];
318 319 320 321 322 323
    _launchView.reset([launchViewController.view retain]);
    _launchView.get().frame = self.view.bounds;
    _launchView.get().autoresizingMask =
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:_launchView.get()];
  }
324 325
}

326 327 328
#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
329
  FXL_CHECK(_platformView != nullptr);
330

331
  // NotifyCreated/NotifyDestroyed are synchronous and require hops between the UI and GPU thread.
332 333 334 335 336 337 338
  if (appeared) {
    _platformView->NotifyCreated();
  } else {
    _platformView->NotifyDestroyed();
  }
}

339 340
#pragma mark - UIViewController lifecycle notifications

341
- (void)viewWillAppear:(BOOL)animated {
342
  TRACE_EVENT0("flutter", "viewWillAppear");
343
  [self connectToEngineAndLoad];
344 345 346 347 348 349
  // Only recreate surface on subsequent appearances when viewport metrics are known.
  // First time surface creation is done on viewDidLayoutSubviews.
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];

350 351 352
  [super viewWillAppear:animated];
}

353 354 355
- (void)viewDidAppear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewDidAppear");
  [self onLocaleUpdated:nil];
356
  [self onUserSettingsChanged:nil];
357 358 359 360 361 362 363 364 365 366 367 368 369
  [self onVoiceOverChanged:nil];
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];

  [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewWillDisappear");
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];

  [super viewWillDisappear:animated];
}

370
- (void)viewDidDisappear:(BOOL)animated {
371 372
  TRACE_EVENT0("flutter", "viewDidDisappear");
  [self surfaceUpdated:NO];
373 374
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];

375
  [super viewDidDisappear:animated];
376 377
}

378 379 380 381 382
- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

383 384 385
#pragma mark - Application lifecycle notifications

- (void)applicationBecameActive:(NSNotification*)notification {
386
  TRACE_EVENT0("flutter", "applicationBecameActive");
387
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.resumed"];
388 389 390
}

- (void)applicationWillResignActive:(NSNotification*)notification {
391
  TRACE_EVENT0("flutter", "applicationWillResignActive");
392 393 394 395
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

- (void)applicationDidEnterBackground:(NSNotification*)notification {
396
  TRACE_EVENT0("flutter", "applicationDidEnterBackground");
397
  [self surfaceUpdated:NO];
398
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];
399 400
}

401
- (void)applicationWillEnterForeground:(NSNotification*)notification {
402
  TRACE_EVENT0("flutter", "applicationWillEnterForeground");
403 404
  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
405 406 407
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.inactive"];
}

408 409 410 411 412 413 414 415
#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

416 417
using PointerChangeMapperPhase = std::pair<blink::PointerData::Change, MapperPhase>;
static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(UITouchPhase phase) {
418 419
  switch (phase) {
    case UITouchPhaseBegan:
420
      return PointerChangeMapperPhase(blink::PointerData::Change::kDown, MapperPhase::Added);
421 422 423 424
    case UITouchPhaseMoved:
    case UITouchPhaseStationary:
      // There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type
      // with the same coordinates
425
      return PointerChangeMapperPhase(blink::PointerData::Change::kMove, MapperPhase::Accessed);
426
    case UITouchPhaseEnded:
427
      return PointerChangeMapperPhase(blink::PointerData::Change::kUp, MapperPhase::Removed);
428
    case UITouchPhaseCancelled:
429
      return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Removed);
430 431
  }

432
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Accessed);
433
}
C
Chinmay Garde 已提交
434

435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
static inline blink::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch,
                                                                     bool touchTypeSupported) {
  if (!touchTypeSupported) {
    return blink::PointerData::DeviceKind::kTouch;
  }

  switch (touch.type) {
    case UITouchTypeDirect:
    case UITouchTypeIndirect:
      return blink::PointerData::DeviceKind::kTouch;
    case UITouchTypeStylus:
      return blink::PointerData::DeviceKind::kStylus;
  }

  return blink::PointerData::DeviceKind::kTouch;
}

C
Chinmay Garde 已提交
452
- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
453 454 455 456 457
  // Note: we cannot rely on touch.phase, since in some cases, e.g.,
  // handleStatusBarTouches, we synthesize touches from existing events.
  //
  // TODO(cbracken) consider creating out own class with the touch fields we
  // need.
A
Adam Barth 已提交
458
  auto eventTypePhase = PointerChangePhaseFromUITouchPhase(phase);
C
Chinmay Garde 已提交
459
  const CGFloat scale = [UIScreen mainScreen].scale;
A
Adam Barth 已提交
460
  auto packet = std::make_unique<blink::PointerDataPacket>(touches.count);
C
Chinmay Garde 已提交
461

A
Adam Barth 已提交
462
  int i = 0;
C
Chinmay Garde 已提交
463
  for (UITouch* touch in touches) {
464
    int device_id = 0;
465 466 467

    switch (eventTypePhase.second) {
      case Accessed:
468
        device_id = _touchMapper.identifierOf(touch);
469 470
        break;
      case Added:
471
        device_id = _touchMapper.registerTouch(touch);
472 473
        break;
      case Removed:
474
        device_id = _touchMapper.unregisterTouch(touch);
475 476
        break;
    }
477

478
    FXL_DCHECK(device_id != 0);
C
Chinmay Garde 已提交
479
    CGPoint windowCoordinates = [touch locationInView:nil];
480

A
Adam Barth 已提交
481 482 483
    blink::PointerData pointer_data;
    pointer_data.Clear();

484 485
    constexpr int kMicrosecondsPerSecond = 1000 * 1000;
    pointer_data.time_stamp = touch.timestamp * kMicrosecondsPerSecond;
486

A
Adam Barth 已提交
487
    pointer_data.change = eventTypePhase.first;
488 489 490

    pointer_data.kind = DeviceKindFromTouchType(touch, _platformSupportsTouchTypes);

491
    pointer_data.device = device_id;
492

A
Adam Barth 已提交
493 494
    pointer_data.physical_x = windowCoordinates.x * scale;
    pointer_data.physical_y = windowCoordinates.y * scale;
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547

    // pressure_min is always 0.0
    if (_platformSupportsTouchPressure) {
      // These properties were introduced in iOS 9.0.
      pointer_data.pressure = touch.force;
      pointer_data.pressure_max = touch.maximumPossibleForce;
    } else {
      pointer_data.pressure = 1.0;
      pointer_data.pressure_max = 1.0;
    }

    // These properties were introduced in iOS 8.0
    pointer_data.radius_major = touch.majorRadius;
    pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
    pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;

    // These properties were introduced in iOS 9.1
    if (_platformSupportsTouchOrientationAndTilt) {
      // iOS Documentation: altitudeAngle
      // A value of 0 radians indicates that the stylus is parallel to the surface. The value of
      // this property is Pi/2 when the stylus is perpendicular to the surface.
      //
      // PointerData Documentation: tilt
      // The angle of the stylus, in radians in the range:
      //    0 <= tilt <= pi/2
      // giving the angle of the axis of the stylus, relative to the axis perpendicular to the input
      // surface (thus 0.0 indicates the stylus is orthogonal to the plane of the input surface,
      // while pi/2 indicates that the stylus is flat on that surface).
      //
      // Discussion:
      // The ranges are the same. Origins are swapped.
      pointer_data.tilt = M_PI_2 - touch.altitudeAngle;

      // iOS Documentation: azimuthAngleInView:
      // With the tip of the stylus touching the screen, the value of this property is 0 radians
      // when the cap end of the stylus (that is, the end opposite of the tip) points along the
      // positive x axis of the device's screen. The azimuth angle increases as the user swings the
      // cap end of the stylus in a clockwise direction around the tip.
      //
      // PointerData Documentation: orientation
      // The angle of the stylus, in radians in the range:
      //    -pi < orientation <= pi
      // giving the angle of the axis of the stylus projected onto the input surface, relative to
      // the positive y-axis of that surface (thus 0.0 indicates the stylus, if projected onto that
      // surface, would go from the contact point vertically up in the positive y-axis direction, pi
      // would indicate that the stylus would go down in the negative y-axis direction; pi/4 would
      // indicate that the stylus goes up and to the right, -pi/2 would indicate that the stylus
      // goes to the left, etc).
      //
      // Discussion:
      // Sweep direction is the same. Phase of M_PI_2.
      pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
    }
A
Adam Barth 已提交
548 549

    packet->SetPointerData(i++, pointer_data);
C
Chinmay Garde 已提交
550
  }
551

552
  blink::Threads::UI()->PostTask(fxl::MakeCopyable(
553 554 555 556
      [ engine = _platformView->engine().GetWeakPtr(), packet = std::move(packet) ] {
        if (engine.get())
          engine->DispatchPointerDataPacket(*packet);
      }));
C
Chinmay Garde 已提交
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
}

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseBegan];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseMoved];
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseEnded];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches phase:UITouchPhaseCancelled];
}

575
#pragma mark - Handle view resizing
576

577
- (void)updateViewportMetrics {
578 579 580 581 582 583 584 585
  blink::Threads::UI()->PostTask(
      [ weak_platform_view = _platformView->GetWeakPtr(), metrics = _viewportMetrics ] {
        if (!weak_platform_view) {
          return;
        }
        weak_platform_view->UpdateSurfaceSize();
        weak_platform_view->engine().SetViewportMetrics(metrics);
      });
586 587
}

588
- (CGFloat)statusBarPadding {
589
  UIScreen* screen = self.view.window.screen;
590
  CGRect statusFrame = [UIApplication sharedApplication].statusBarFrame;
591 592
  CGRect viewFrame =
      [self.view convertRect:self.view.bounds toCoordinateSpace:screen.coordinateSpace];
593 594
  CGRect intersection = CGRectIntersection(statusFrame, viewFrame);
  return CGRectIsNull(intersection) ? 0.0 : intersection.size.height;
595 596
}

597
- (void)viewDidLayoutSubviews {
598
  CGSize viewSize = self.view.bounds.size;
599 600
  CGFloat scale = [UIScreen mainScreen].scale;

601 602
  // First time since creation that the dimensions of its view is known.
  bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
603
  _viewportMetrics.device_pixel_ratio = scale;
604 605
  _viewportMetrics.physical_width = viewSize.width * scale;
  _viewportMetrics.physical_height = viewSize.height * scale;
606 607 608 609 610 611 612 613 614 615 616 617 618 619

  // TODO(cbracken) once clang toolchain compiler-rt has been updated, replace with
  // if (@available(iOS 11, *)) {
  if (_platformSupportsSafeAreaInsets) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
    _viewportMetrics.physical_padding_top = self.view.safeAreaInsets.top * scale;
    _viewportMetrics.physical_padding_left = self.view.safeAreaInsets.left * scale;
    _viewportMetrics.physical_padding_right = self.view.safeAreaInsets.right * scale;
    _viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
#pragma clang diagnostic pop
  } else {
    _viewportMetrics.physical_padding_top = [self statusBarPadding] * scale;
  }
620
  [self updateViewportMetrics];
621 622 623 624 625

  // This must run after updateViewportMetrics so that the surface creation tasks are queued after
  // the viewport metrics update tasks.
  if (firstViewBoundsUpdate)
    [self surfaceUpdated:YES];
626 627
}

628
#pragma mark - Keyboard events
629

630
- (void)keyboardWillChangeFrame:(NSNotification*)notification {
631
  NSDictionary* info = [notification userInfo];
632
  CGFloat bottom = CGRectGetHeight([[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]);
633
  CGFloat scale = [UIScreen mainScreen].scale;
634 635
  _viewportMetrics.physical_padding_bottom = bottom * scale;
  [self updateViewportMetrics];
636 637
}

638
- (void)keyboardWillBeHidden:(NSNotification*)notification {
639 640 641 642 643 644 645 646 647 648 649
  // TODO(cbracken) once clang toolchain compiler-rt has been updated, replace with
  // if (@available(iOS 11, *)) {
  if (_platformSupportsSafeAreaInsets) {
    CGFloat scale = [UIScreen mainScreen].scale;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
    _viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
#pragma clang diagnostic pop
  } else {
    _viewportMetrics.physical_padding_bottom = 0;
  }
650
  [self updateViewportMetrics];
651 652
}

653 654 655
#pragma mark - Text input delegate

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
656 657
  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
                              arguments:@[ @(client), state ]];
658 659
}

660 661 662 663 664 665
- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
  NSString* actionString;
  switch (action) {
    case FlutterTextInputActionDone:
      actionString = @"TextInputAction.done";
      break;
666 667 668
    case FlutterTextInputActionNewline:
      actionString = @"TextInputAction.newline";
      break;
669 670 671 672 673
  }
  [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction"
                              arguments:@[ @(client), actionString ]];
}

674 675 676 677 678 679 680
#pragma mark - Orientation updates

- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
  // Notifications may not be on the iOS UI thread
  dispatch_async(dispatch_get_main_queue(), ^{
    NSDictionary* info = notification.userInfo;

681
    NSNumber* update = info[@(shell::kOrientationUpdateNotificationKey)];
682 683 684 685 686 687 688 689 690 691 692 693

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
694 695
}

696 697
- (BOOL)shouldAutorotate {
  return YES;
698 699
}

700 701 702 703
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

704 705 706 707 708 709 710
#pragma mark - Accessibility

- (void)onVoiceOverChanged:(NSNotification*)notification {
#if TARGET_OS_SIMULATOR
  // There doesn't appear to be any way to determine whether the accessibility
  // inspector is enabled on the simulator. We conservatively always turn on the
  // accessibility bridge in the simulator.
711
  bool enabled = true;
712
#else
713
  bool enabled = UIAccessibilityIsVoiceOverRunning();
714
#endif
715
  _platformView->ToggleAccessibility(self.view, enabled);
716 717
}

718 719 720
#pragma mark - Memory Notifications

- (void)onMemoryWarning:(NSNotification*)notification {
721
  [_systemChannel.get() sendMessage:@{@"type" : @"memoryPressure"}];
722 723
}

724 725 726 727 728 729
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
  NSLocale* currentLocale = [NSLocale currentLocale];
  NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
  NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
730
  [_localizationChannel.get() invokeMethod:@"setLocale" arguments:@[ languageCode, countryCode ]];
731 732
}

733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
#pragma mark - Set user settings

- (void)onUserSettingsChanged:(NSNotification*)notification {
  [_settingsChannel.get() sendMessage:@{
    @"textScaleFactor" : @([self textScaleFactor]),
    @"alwaysUse24HourFormat" : @([self isAlwaysUse24HourFormat]),
  }];
}

- (CGFloat)textScaleFactor {
  UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory;
  // The delta is computed based on the following:
  // - L (large) is the default 1.0 scale.
  // - The scale is linear spanning from XS to XXXL.
  // - XXXL = 1.4 * XS.
  //
  // L    = 1.0      = XS + 3 * delta
  // XXXL = 1.4 * XS = XS + 6 * delta
  const CGFloat delta = 0.055555;
  if ([category isEqualToString:UIContentSizeCategoryExtraSmall])
    return 1.0 - 3 * delta;
  else if ([category isEqualToString:UIContentSizeCategorySmall])
    return 1.0 - 2 * delta;
  else if ([category isEqualToString:UIContentSizeCategoryMedium])
    return 1.0 - delta;
  else if ([category isEqualToString:UIContentSizeCategoryLarge])
    return 1.0;
  else if ([category isEqualToString:UIContentSizeCategoryExtraLarge])
    return 1.0 + delta;
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge])
    return 1.0 + 2 * delta;
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge])
    return 1.0 + 3 * delta;
  else
    return 1.0;
}

- (BOOL)isAlwaysUse24HourFormat {
  // iOS does not report its "24-Hour Time" user setting in the API. Instead, it applies
  // it automatically to NSDateFormatter when used with [NSLocale currentLocale]. It is
  // essential that [NSLocale currentLocale] is used. Any custom locale, even the one
  // that's the same as [NSLocale currentLocale] will ignore the 24-hour option (there
  // must be some internal field that's not exposed to developers).
  //
  // Therefore this option behaves differently across Android and iOS. On Android this
  // setting is exposed standalone, and can therefore be applied to all locales, whether
  // the "current system locale" or a custom one. On iOS it only applies to the current
  // system locale. Widget implementors must take this into account in order to provide
  // platform-idiomatic behavior in their widgets.
  NSString* dateFormat =
      [NSDateFormatter dateFormatFromTemplate:@"j" options:0 locale:[NSLocale currentLocale]];
  return [dateFormat rangeOfString:@"a"].location == NSNotFound;
}

787 788 789 790 791
#pragma mark - Status Bar touch event handling

// Standard iOS status bar height in pixels.
constexpr CGFloat kStandardStatusBarHeight = 20.0;

792
- (void)handleStatusBarTouches:(UIEvent*)event {
793 794 795 796 797 798 799 800
  // If the status bar is double-height, don't handle status bar taps. iOS
  // should open the app associated with the status bar.
  CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
  if (statusBarFrame.size.height != kStandardStatusBarHeight) {
    return;
  }

  // If we detect a touch in the status bar, synthesize a fake touch begin/end.
801
  for (UITouch* touch in event.allTouches) {
802 803 804 805
    if (touch.phase == UITouchPhaseBegan && touch.tapCount > 0) {
      CGPoint windowLoc = [touch locationInView:nil];
      CGPoint screenLoc = [touch.window convertPoint:windowLoc toWindow:nil];
      if (CGRectContainsPoint(statusBarFrame, screenLoc)) {
806
        NSSet* statusbarTouches = [NSSet setWithObject:touch];
807 808 809 810 811 812 813 814
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseBegan];
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseEnded];
        return;
      }
    }
  }
}

815 816 817 818 819 820 821 822 823 824 825
#pragma mark - Status bar style

- (UIStatusBarStyle)preferredStatusBarStyle {
  return _statusBarStyle;
}

- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
  // Notifications may not be on the iOS UI thread
  dispatch_async(dispatch_get_main_queue(), ^{
    NSDictionary* info = notification.userInfo;

826
    NSNumber* update = info[@(shell::kOverlayStyleUpdateNotificationKey)];
827 828 829 830 831 832 833 834 835 836 837 838 839 840

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

    if (style != _statusBarStyle) {
      _statusBarStyle = static_cast<UIStatusBarStyle>(style);
      [self setNeedsStatusBarAppearanceUpdate];
    }
  });
}

841
#pragma mark - FlutterBinaryMessenger
842

843 844
- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
  [self sendOnChannel:channel message:message binaryReply:nil];
845 846
}

847 848 849
- (void)sendOnChannel:(NSString*)channel
              message:(NSData*)message
          binaryReply:(FlutterBinaryReply)callback {
850
  NSAssert(channel, @"The channel must not be null");
851
  fxl::RefPtr<PlatformMessageResponseDarwin> response =
852
      (callback == nil) ? nullptr
853
                        : fxl::MakeRefCounted<PlatformMessageResponseDarwin>(^(NSData* reply) {
854 855
                            callback(reply);
                          });
856 857 858
  fxl::RefPtr<blink::PlatformMessage> platformMessage =
      (message == nil) ? fxl::MakeRefCounted<blink::PlatformMessage>(channel.UTF8String, response)
                       : fxl::MakeRefCounted<blink::PlatformMessage>(
859
                             channel.UTF8String, shell::GetVectorFromNSData(message), response);
860
  _platformView->DispatchPlatformMessage(platformMessage);
861 862
}

863 864 865
- (void)setMessageHandlerOnChannel:(NSString*)channel
              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
  NSAssert(channel, @"The channel must not be null");
866
  _platformView->platform_message_router().SetMessageHandler(channel.UTF8String, handler);
867
}
C
Chinmay Garde 已提交
868
@end