FlutterViewController.mm 33.4 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/lib/ui/painting/resource_context.h"
15
#include "flutter/shell/platform/darwin/common/buffer_conversions.h"
16
#include "flutter/shell/platform/darwin/common/platform_mac.h"
17
#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h"
18
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
19
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
20 21
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
22
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_main_ios.h"
23
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.h"
24
#include "flutter/shell/platform/darwin/ios/ios_external_texture_gl.h"
25
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
26 27
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/time/time_delta.h"
28

29 30
namespace {

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

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

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

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

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

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

}  // namespace

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

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

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

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

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

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

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

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

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

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

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

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

195 196
  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
  _textInputPlugin.get().textInputDelegate = self;
197 198 199
  [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    [_textInputPlugin.get() handleMethodCall:call result:result];
  }];
Y
Yegor 已提交
200
  _platformView->SetTextInputPlugin(_textInputPlugin);
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 402
  // GrContext operations are blocked when the app is in the background.
  blink::ResourceContext::Freeze();
403
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];
404 405
}

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

414 415 416 417 418 419 420 421
#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

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

438
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel, MapperPhase::Accessed);
439
}
C
Chinmay Garde 已提交
440

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

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

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

484
    FXL_DCHECK(device_id != 0);
485
    CGPoint windowCoordinates = [touch locationInView:self.view];
486

A
Adam Barth 已提交
487 488 489
    blink::PointerData pointer_data;
    pointer_data.Clear();

490 491
    constexpr int kMicrosecondsPerSecond = 1000 * 1000;
    pointer_data.time_stamp = touch.timestamp * kMicrosecondsPerSecond;
492

A
Adam Barth 已提交
493
    pointer_data.change = eventTypePhase.first;
494 495 496

    pointer_data.kind = DeviceKindFromTouchType(touch, _platformSupportsTouchTypes);

497
    pointer_data.device = device_id;
498

A
Adam Barth 已提交
499 500
    pointer_data.physical_x = windowCoordinates.x * scale;
    pointer_data.physical_y = windowCoordinates.y * scale;
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 551 552 553

    // 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 已提交
554 555

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

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

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

581
#pragma mark - Handle view resizing
582

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

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

603
- (void)viewDidLayoutSubviews {
604
  CGSize viewSize = self.view.bounds.size;
605 606
  CGFloat scale = [UIScreen mainScreen].scale;

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

613 614 615 616 617 618 619 620 621 622
  [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 {
623 624 625
  [self updateViewportPadding];
  [self updateViewportMetrics];
  [super viewSafeAreaInsetsDidChange];
626 627 628 629 630 631 632
}

// Updates _viewportMetrics physical padding.
//
// Viewport padding represents the iOS safe area insets.
- (void)updateViewportPadding {
  CGFloat scale = [UIScreen mainScreen].scale;
633
  if (@available(iOS 11, *)) {
634 635 636
    _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;
637
    _viewportMetrics.physical_padding_bottom = self.view.safeAreaInsets.bottom * scale;
638 639 640
  } else {
    _viewportMetrics.physical_padding_top = [self statusBarPadding] * scale;
  }
641 642
}

643
#pragma mark - Keyboard events
644

645
- (void)keyboardWillChangeFrame:(NSNotification*)notification {
646
  NSDictionary* info = [notification userInfo];
647
  CGFloat bottom = CGRectGetHeight([[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]);
648
  CGFloat scale = [UIScreen mainScreen].scale;
649
  _viewportMetrics.physical_view_inset_bottom = bottom * scale;
650
  [self updateViewportMetrics];
651 652
}

653
- (void)keyboardWillBeHidden:(NSNotification*)notification {
654
  _viewportMetrics.physical_view_inset_bottom = 0;
655
  [self updateViewportMetrics];
656 657
}

658 659 660
#pragma mark - Text input delegate

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
661 662
  [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
                              arguments:@[ @(client), state ]];
663 664
}

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

679 680 681 682 683 684 685
#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;

686
    NSNumber* update = info[@(shell::kOrientationUpdateNotificationKey)];
687 688 689 690 691 692 693 694 695 696 697 698

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
699 700
}

701 702
- (BOOL)shouldAutorotate {
  return YES;
703 704
}

705 706 707 708
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

709 710 711 712 713 714 715
#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.
716
  bool enabled = true;
717
#else
718
  bool enabled = UIAccessibilityIsVoiceOverRunning();
719
#endif
720
  _platformView->ToggleAccessibility(self.view, enabled);
721 722
}

723 724 725
#pragma mark - Memory Notifications

- (void)onMemoryWarning:(NSNotification*)notification {
726
  [_systemChannel.get() sendMessage:@{@"type" : @"memoryPressure"}];
727 728
}

729 730 731 732 733 734
#pragma mark - Locale updates

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

738 739 740 741 742 743 744 745 746 747 748
#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;
749 750
  // The delta is computed by approximating Apple's typography guidelines:
  // https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/
751
  //
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
  // Specifically:
  // Non-accessibility sizes for "body" text are:
  const CGFloat xs = 14;
  const CGFloat s = 15;
  const CGFloat m = 16;
  const CGFloat l = 17;
  const CGFloat xl = 19;
  const CGFloat xxl = 21;
  const CGFloat xxxl = 23;

  // Accessibility sizes for "body" text are:
  const CGFloat ax1 = 28;
  const CGFloat ax2 = 33;
  const CGFloat ax3 = 40;
  const CGFloat ax4 = 47;
  const CGFloat ax5 = 53;

  // We compute the scale as relative difference from size L (large, the default size), where
  // L is assumed to have scale 1.0.
771
  if ([category isEqualToString:UIContentSizeCategoryExtraSmall])
772
    return xs / l;
773
  else if ([category isEqualToString:UIContentSizeCategorySmall])
774
    return s / l;
775
  else if ([category isEqualToString:UIContentSizeCategoryMedium])
776
    return m / l;
777 778 779
  else if ([category isEqualToString:UIContentSizeCategoryLarge])
    return 1.0;
  else if ([category isEqualToString:UIContentSizeCategoryExtraLarge])
780
    return xl / l;
781
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge])
782
    return xxl / l;
783
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge])
784 785 786 787 788 789 790 791 792 793 794
    return xxxl / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium])
    return ax1 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge])
    return ax2 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge])
    return ax3 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge])
    return ax4 / l;
  else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge])
    return ax5 / l;
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
  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;
}

816 817 818 819 820
#pragma mark - Status Bar touch event handling

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

821
- (void)handleStatusBarTouches:(UIEvent*)event {
822
  CGFloat standardStatusBarHeight = kStandardStatusBarHeight;
823
  if (@available(iOS 11, *)) {
824 825 826
    standardStatusBarHeight = self.view.safeAreaInsets.top;
  }

827 828 829
  // 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;
830
  if (statusBarFrame.size.height != standardStatusBarHeight) {
831 832 833 834
    return;
  }

  // If we detect a touch in the status bar, synthesize a fake touch begin/end.
835
  for (UITouch* touch in event.allTouches) {
836 837 838 839
    if (touch.phase == UITouchPhaseBegan && touch.tapCount > 0) {
      CGPoint windowLoc = [touch locationInView:nil];
      CGPoint screenLoc = [touch.window convertPoint:windowLoc toWindow:nil];
      if (CGRectContainsPoint(statusBarFrame, screenLoc)) {
840
        NSSet* statusbarTouches = [NSSet setWithObject:touch];
841 842 843 844 845 846 847 848
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseBegan];
        [self dispatchTouches:statusbarTouches phase:UITouchPhaseEnded];
        return;
      }
    }
  }
}

849 850 851 852 853 854 855 856 857 858 859
#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;

860
    NSNumber* update = info[@(shell::kOverlayStyleUpdateNotificationKey)];
861 862 863 864 865 866 867 868 869 870 871 872 873 874

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

875
#pragma mark - FlutterBinaryMessenger
876

877 878
- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
  [self sendOnChannel:channel message:message binaryReply:nil];
879 880
}

881 882 883
- (void)sendOnChannel:(NSString*)channel
              message:(NSData*)message
          binaryReply:(FlutterBinaryReply)callback {
884
  NSAssert(channel, @"The channel must not be null");
885
  fxl::RefPtr<PlatformMessageResponseDarwin> response =
886
      (callback == nil) ? nullptr
887
                        : fxl::MakeRefCounted<PlatformMessageResponseDarwin>(^(NSData* reply) {
888 889
                            callback(reply);
                          });
890 891 892
  fxl::RefPtr<blink::PlatformMessage> platformMessage =
      (message == nil) ? fxl::MakeRefCounted<blink::PlatformMessage>(channel.UTF8String, response)
                       : fxl::MakeRefCounted<blink::PlatformMessage>(
893
                             channel.UTF8String, shell::GetVectorFromNSData(message), response);
894
  _platformView->DispatchPlatformMessage(platformMessage);
895 896
}

897 898 899
- (void)setMessageHandlerOnChannel:(NSString*)channel
              binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
  NSAssert(channel, @"The channel must not be null");
900
  _platformView->platform_message_router().SetMessageHandler(channel.UTF8String, handler);
901
}
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917

#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 已提交
918
@end