FlutterViewController.mm 33.0 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/flow/texture.h"
11
#include "flutter/fml/platform/darwin/platform_version.h"
12 13
#include "flutter/fml/platform/darwin/scoped_block.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
14
#include "flutter/shell/platform/darwin/common/buffer_conversions.h"
15
#include "flutter/shell/platform/darwin/common/platform_mac.h"
16
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
17
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
18
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
19 20
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
21
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_main_ios.h"
22
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.h"
23
#include "flutter/shell/platform/darwin/ios/ios_external_texture_gl.h"
24
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
25 26
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/time/time_delta.h"
27

28 29
namespace {

30
typedef void (^PlatformMessageResponseCallback)(NSData*);
31 32 33 34 35 36

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

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

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

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

54
  fml::ScopedBlock<PlatformMessageResponseCallback> callback_;
55 56 57 58
};

}  // namespace

59
@interface FlutterViewController ()<UIAlertViewDelegate, FlutterTextInputDelegate>
60 61
@end

62
@implementation FlutterViewController {
63
  fml::scoped_nsprotocol<FlutterDartProject*> _dartProject;
64
  UIInterfaceOrientationMask _orientationPreferences;
65
  UIStatusBarStyle _statusBarStyle;
66
  blink::ViewportMetrics _viewportMetrics;
67
  shell::TouchMapper _touchMapper;
M
Michael Goderbauer 已提交
68
  std::shared_ptr<shell::PlatformViewIOS> _platformView;
69 70 71 72 73 74
  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;
75 76
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _lifecycleChannel;
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _systemChannel;
77
  fml::scoped_nsprotocol<FlutterBasicMessageChannel*> _settingsChannel;
78
  fml::scoped_nsprotocol<UIView*> _launchView;
79
  int64_t _nextTextureId;
80 81 82
  bool _platformSupportsTouchTypes;
  bool _platformSupportsTouchPressure;
  bool _platformSupportsTouchOrientationAndTilt;
83
  bool _platformSupportsSafeAreaInsets;
84
  BOOL _initialized;
85
  BOOL _connected;
86 87
}

88 89 90 91 92 93
+ (void)initialize {
  if (self == [FlutterViewController class]) {
    shell::FlutterMain();
  }
}

94 95
#pragma mark - Manage and override all designated initializers

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

101
  if (self) {
102
    if (project == nil)
103
      _dartProject.reset([[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]);
104 105
    else
      _dartProject.reset([project retain]);
106 107

    [self performCommonViewControllerInitialization];
108
  }
109

110
  return self;
C
Chinmay Garde 已提交
111 112
}

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

117
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
118
  return [self initWithProject:nil nibName:nil bundle:nil];
119 120
}

121
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
122

123
- (void)performCommonViewControllerInitialization {
124
  if (_initialized)
125
    return;
126

127
  _initialized = YES;
C
Chinmay Garde 已提交
128

129 130 131
  _platformSupportsTouchTypes = fml::IsPlatformVersionAtLeast(9);
  _platformSupportsTouchPressure = fml::IsPlatformVersionAtLeast(9);
  _platformSupportsTouchOrientationAndTilt = fml::IsPlatformVersionAtLeast(9, 1);
132
  _platformSupportsSafeAreaInsets = fml::IsPlatformVersionAtLeast(11, 0);
133

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

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

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

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

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

  _textInputChannel.reset([[FlutterMethodChannel alloc]
172
         initWithName:@"flutter/textinput"
173
      binaryMessenger:self
174
                codec:[FlutterJSONMethodCodec sharedInstance]]);
175

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

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

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

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

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

202
  [self setupNotificationCenterObservers];
C
Chinmay Garde 已提交
203 204
}

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

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

217 218 219 220 221 222 223 224 225 226
  [center addObserver:self
             selector:@selector(applicationBecameActive:)
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];

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

227 228 229 230 231 232 233 234 235 236
  [center addObserver:self
             selector:@selector(applicationDidEnterBackground:)
                 name:UIApplicationDidEnterBackgroundNotification
               object:nil];

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

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

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

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];
251 252 253 254 255

  [center addObserver:self
             selector:@selector(onVoiceOverChanged:)
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];
256 257 258 259 260

  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];
261 262 263 264 265

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

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

273
- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
274 275 276
  exit(0);
}

277
- (void)connectToEngineAndLoad {
278 279 280 281
  if (_connected)
    return;
  _connected = YES;

C
Chinmay Garde 已提交
282
  TRACE_EVENT0("flutter", "connectToEngineAndLoad");
283

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

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

#pragma mark - Loading the view

- (void)loadView {
305
  FlutterView* view = [[FlutterView alloc] init];
306

307
  self.view = view;
308
  self.view.multipleTouchEnabled = YES;
309
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
310

311
  [view release];
312 313 314 315 316 317 318

  // 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 =
319 320
        [[UIStoryboard storyboardWithName:launchStoryboardName bundle:nil]
            instantiateInitialViewController];
321 322 323 324 325 326
    _launchView.reset([launchViewController.view retain]);
    _launchView.get().frame = self.view.bounds;
    _launchView.get().autoresizingMask =
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:_launchView.get()];
  }
327 328
}

329 330 331
#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
332
  FXL_CHECK(_platformView != nullptr);
333

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

342 343
#pragma mark - UIViewController lifecycle notifications

344
- (void)viewWillAppear:(BOOL)animated {
345
  TRACE_EVENT0("flutter", "viewWillAppear");
346
  [self connectToEngineAndLoad];
347 348 349 350 351 352
  // 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"];

353 354 355
  [super viewWillAppear:animated];
}

356 357 358
- (void)viewDidAppear:(BOOL)animated {
  TRACE_EVENT0("flutter", "viewDidAppear");
  [self onLocaleUpdated:nil];
359
  [self onUserSettingsChanged:nil];
360 361 362 363 364 365 366 367 368 369 370 371 372
  [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];
}

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

378
  [super viewDidDisappear:animated];
379 380
}

381 382 383 384 385
- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

386 387 388
#pragma mark - Application lifecycle notifications

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

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

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

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

411 412 413 414 415 416 417 418
#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

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

435
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Accessed);
436
}
C
Chinmay Garde 已提交
437

438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
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 已提交
455
- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
456 457 458 459 460
  // 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 已提交
461
  auto eventTypePhase = PointerChangePhaseFromUITouchPhase(phase);
C
Chinmay Garde 已提交
462
  const CGFloat scale = [UIScreen mainScreen].scale;
A
Adam Barth 已提交
463
  auto packet = std::make_unique<blink::PointerDataPacket>(touches.count);
C
Chinmay Garde 已提交
464

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

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

481
    FXL_DCHECK(device_id != 0);
C
Chinmay Garde 已提交
482
    CGPoint windowCoordinates = [touch locationInView:nil];
483

A
Adam Barth 已提交
484 485 486
    blink::PointerData pointer_data;
    pointer_data.Clear();

487 488
    constexpr int kMicrosecondsPerSecond = 1000 * 1000;
    pointer_data.time_stamp = touch.timestamp * kMicrosecondsPerSecond;
489

A
Adam Barth 已提交
490
    pointer_data.change = eventTypePhase.first;
491 492 493

    pointer_data.kind = DeviceKindFromTouchType(touch, _platformSupportsTouchTypes);

494
    pointer_data.device = device_id;
495

A
Adam Barth 已提交
496 497
    pointer_data.physical_x = windowCoordinates.x * scale;
    pointer_data.physical_y = windowCoordinates.y * scale;
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 548 549 550

    // 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 已提交
551 552

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

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

- (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];
}

578
#pragma mark - Handle view resizing
579

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

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

600
- (void)viewDidLayoutSubviews {
601
  CGSize viewSize = self.view.bounds.size;
602 603
  CGFloat scale = [UIScreen mainScreen].scale;

604 605
  // First time since creation that the dimensions of its view is known.
  bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
606
  _viewportMetrics.device_pixel_ratio = scale;
607 608
  _viewportMetrics.physical_width = viewSize.width * scale;
  _viewportMetrics.physical_height = viewSize.height * scale;
609

610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
  [self updateViewportPadding];
  [self updateViewportMetrics];

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

- (void)viewSafeAreaInsetsDidChange {
  if (_platformSupportsSafeAreaInsets) {
    [self updateViewportPadding];
    [self updateViewportMetrics];
    [super viewSafeAreaInsetsDidChange];
  }
}

// Updates _viewportMetrics physical padding.
//
// Viewport padding represents the iOS safe area insets.
- (void)updateViewportPadding {
  CGFloat scale = [UIScreen mainScreen].scale;
632 633 634 635 636 637 638 639
  // 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;
640 641
    // TODO(cbracken): Once framework has been updated to use view insets for the keyboard, enable
    // bottom safe area padding.
642
    // _viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
643 644 645 646
#pragma clang diagnostic pop
  } else {
    _viewportMetrics.physical_padding_top = [self statusBarPadding] * scale;
  }
647 648
}

649
#pragma mark - Keyboard events
650

651
- (void)keyboardWillChangeFrame:(NSNotification*)notification {
652
  NSDictionary* info = [notification userInfo];
653
  CGFloat bottom = CGRectGetHeight([[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]);
654
  CGFloat scale = [UIScreen mainScreen].scale;
655 656 657
  // TODO(cbracken): Once framework has been updated to use view insets, keyboard should change
  // insets rather than padding.
  // _viewportMetrics.physical_view_inset_bottom = bottom * scale;
658 659
  _viewportMetrics.physical_padding_bottom = bottom * scale;
  [self updateViewportMetrics];
660 661
}

662
- (void)keyboardWillBeHidden:(NSNotification*)notification {
663 664 665
  // TODO(cbracken): Once framework has been updated to use view insets, keyboard should change
  // insets rather than padding.
  // _viewportMetrics.physical_view_inset_bottom = 0;
666
  _viewportMetrics.physical_padding_bottom = 0;
667
  [self updateViewportMetrics];
668 669
}

670 671 672
#pragma mark - Text input delegate

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
673 674
  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
                              arguments:@[ @(client), state ]];
675 676
}

677 678 679 680 681 682
- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
  NSString* actionString;
  switch (action) {
    case FlutterTextInputActionDone:
      actionString = @"TextInputAction.done";
      break;
683 684 685
    case FlutterTextInputActionNewline:
      actionString = @"TextInputAction.newline";
      break;
686 687 688 689 690
  }
  [_textInputChannel.get() invokeMethod:@"TextInputClient.performAction"
                              arguments:@[ @(client), actionString ]];
}

691 692 693 694 695 696 697
#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;

698
    NSNumber* update = info[@(shell::kOrientationUpdateNotificationKey)];
699 700 701 702 703 704 705 706 707 708 709 710

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
711 712
}

713 714
- (BOOL)shouldAutorotate {
  return YES;
715 716
}

717 718 719 720
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

721 722 723 724 725 726 727
#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.
728
  bool enabled = true;
729
#else
730
  bool enabled = UIAccessibilityIsVoiceOverRunning();
731
#endif
732
  _platformView->ToggleAccessibility(self.view, enabled);
733 734
}

735 736 737
#pragma mark - Memory Notifications

- (void)onMemoryWarning:(NSNotification*)notification {
738
  [_systemChannel.get() sendMessage:@{@"type" : @"memoryPressure"}];
739 740
}

741 742 743 744 745 746
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
  NSLocale* currentLocale = [NSLocale currentLocale];
  NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
  NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
747
  [_localizationChannel.get() invokeMethod:@"setLocale" arguments:@[ languageCode, countryCode ]];
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 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
#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;
}

804 805 806 807 808
#pragma mark - Status Bar touch event handling

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

809
- (void)handleStatusBarTouches:(UIEvent*)event {
810 811 812 813 814 815 816 817
  // 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.
818
  for (UITouch* touch in event.allTouches) {
819 820 821 822
    if (touch.phase == UITouchPhaseBegan && touch.tapCount > 0) {
      CGPoint windowLoc = [touch locationInView:nil];
      CGPoint screenLoc = [touch.window convertPoint:windowLoc toWindow:nil];
      if (CGRectContainsPoint(statusBarFrame, screenLoc)) {
823
        NSSet* statusbarTouches = [NSSet setWithObject:touch];
824 825 826 827 828 829 830 831
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseBegan];
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseEnded];
        return;
      }
    }
  }
}

832 833 834 835 836 837 838 839 840 841 842
#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;

843
    NSNumber* update = info[@(shell::kOverlayStyleUpdateNotificationKey)];
844 845 846 847 848 849 850 851 852 853 854 855 856 857

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

858
#pragma mark - FlutterBinaryMessenger
859

860 861
- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
  [self sendOnChannel:channel message:message binaryReply:nil];
862 863
}

864 865 866
- (void)sendOnChannel:(NSString*)channel
              message:(NSData*)message
          binaryReply:(FlutterBinaryReply)callback {
867
  NSAssert(channel, @"The channel must not be null");
868
  fxl::RefPtr<PlatformMessageResponseDarwin> response =
869
      (callback == nil) ? nullptr
870
                        : fxl::MakeRefCounted<PlatformMessageResponseDarwin>(^(NSData* reply) {
871 872
                            callback(reply);
                          });
873 874 875
  fxl::RefPtr<blink::PlatformMessage> platformMessage =
      (message == nil) ? fxl::MakeRefCounted<blink::PlatformMessage>(channel.UTF8String, response)
                       : fxl::MakeRefCounted<blink::PlatformMessage>(
876
                             channel.UTF8String, shell::GetVectorFromNSData(message), response);
877
  _platformView->DispatchPlatformMessage(platformMessage);
878 879
}

880 881 882
- (void)setMessageHandlerOnChannel:(NSString*)channel
              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
  NSAssert(channel, @"The channel must not be null");
883
  _platformView->platform_message_router().SetMessageHandler(channel.UTF8String, handler);
884
}
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900

#pragma mark - FlutterTextureRegistry

- (int64_t)registerTexture:(NSObject<FlutterTexture>*)texture {
  int64_t textureId = _nextTextureId++;
  _platformView->RegisterExternalTexture(textureId, texture);
  return textureId;
}

- (void)unregisterTexture:(int64_t)textureId {
  _platformView->UnregisterTexture(textureId);
}

- (void)textureFrameAvailable:(int64_t)textureId {
  _platformView->MarkTextureFrameAvailable(textureId);
}
C
Chinmay Garde 已提交
901
@end