FlutterViewController.mm 33.3 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];
  }];
200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#pragma mark - Loading the view

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

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

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

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

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

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

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

341 342
#pragma mark - UIViewController lifecycle notifications

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

352 353 354
  [super viewWillAppear:animated];
}

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

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

377
  [super viewDidDisappear:animated];
378 379
}

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

385 386 387
#pragma mark - Application lifecycle notifications

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

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

- (void)applicationDidEnterBackground:(NSNotification*)notification {
398
  TRACE_EVENT0("flutter", "applicationDidEnterBackground");
399
  [self surfaceUpdated:NO];
400 401
  // GrContext operations are blocked when the app is in the background.
  blink::ResourceContext::Freeze();
402
  [_lifecycleChannel.get() sendMessage:@"AppLifecycleState.paused"];
403 404
}

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

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

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

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

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

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

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

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

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

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

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

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

    pointer_data.kind = DeviceKindFromTouchType(touch, _platformSupportsTouchTypes);

496
    pointer_data.device = device_id;
497

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

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

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

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

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

580
#pragma mark - Handle view resizing
581

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

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

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

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

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

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

642
#pragma mark - Keyboard events
643

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

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

657 658 659
#pragma mark - Text input delegate

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

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

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

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

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

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

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

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

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

722 723 724
#pragma mark - Memory Notifications

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

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

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

737 738 739 740 741 742 743 744 745 746 747
#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;
748 749
  // The delta is computed by approximating Apple's typography guidelines:
  // https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/
750
  //
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
  // 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.
770
  if ([category isEqualToString:UIContentSizeCategoryExtraSmall])
771
    return xs / l;
772
  else if ([category isEqualToString:UIContentSizeCategorySmall])
773
    return s / l;
774
  else if ([category isEqualToString:UIContentSizeCategoryMedium])
775
    return m / l;
776 777 778
  else if ([category isEqualToString:UIContentSizeCategoryLarge])
    return 1.0;
  else if ([category isEqualToString:UIContentSizeCategoryExtraLarge])
779
    return xl / l;
780
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge])
781
    return xxl / l;
782
  else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge])
783 784 785 786 787 788 789 790 791 792 793
    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;
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
  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;
}

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

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

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

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

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

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

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

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

874
#pragma mark - FlutterBinaryMessenger
875

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

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

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

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