FlutterViewController.mm 16.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
#include <memory>

9 10 11
#include "base/mac/scoped_block.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
A
Adam Barth 已提交
12
#include "flutter/common/threads.h"
13 14 15
#include "flutter/shell/gpu/gpu_rasterizer.h"
#include "flutter/shell/gpu/gpu_surface_gl.h"
#include "flutter/shell/platform/darwin/common/platform_mac.h"
A
Adam Barth 已提交
16
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.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
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"
A
Adam Barth 已提交
20
#include "lib/ftl/functional/make_copyable.h"
21
#include "lib/ftl/time/time_delta.h"
22

23
@interface FlutterViewController ()<UIAlertViewDelegate>
24 25
@end

26 27 28
void FlutterInit(int argc, const char* argv[]) {
  NSBundle* bundle = [NSBundle bundleForClass:[FlutterViewController class]];
  NSString* icuDataPath = [bundle pathForResource:@"icudtl" ofType:@"dat"];
29
  shell::PlatformMacMain(argc, argv, icuDataPath.UTF8String);
30 31
}

32
@implementation FlutterViewController {
33
  base::scoped_nsprotocol<FlutterDartProject*> _dartProject;
34
  UIInterfaceOrientationMask _orientationPreferences;
35
  UIStatusBarStyle _statusBarStyle;
36
  sky::ViewportMetricsPtr _viewportMetrics;
37 38
  shell::TouchMapper _touchMapper;
  std::unique_ptr<shell::PlatformViewIOS> _platformView;
39
  base::scoped_nsprotocol<FlutterPlatformPlugin*> _platformPlugin;
40

41
  BOOL _initialized;
42 43
}

44 45
#pragma mark - Manage and override all designated initializers

46 47 48 49
- (instancetype)initWithProject:(FlutterDartProject*)project
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
50

51
  if (self) {
52
    if (project == nil)
53 54
      _dartProject.reset(
          [[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]);
55 56
    else
      _dartProject.reset([project retain]);
57 58

    [self performCommonViewControllerInitialization];
59
  }
60

61
  return self;
C
Chinmay Garde 已提交
62 63
}

64 65
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
66
  return [self initWithProject:nil nibName:nil bundle:nil];
C
Chinmay Garde 已提交
67 68
}

69
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
70
  return [self initWithProject:nil nibName:nil bundle:nil];
71 72
}

73
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
74

75
- (void)performCommonViewControllerInitialization {
76
  if (_initialized)
77 78
    return;
  _initialized = YES;
C
Chinmay Garde 已提交
79

80
  _orientationPreferences = UIInterfaceOrientationMaskAll;
81
  _statusBarStyle = UIStatusBarStyleDefault;
82
  _viewportMetrics = sky::ViewportMetrics::New();
A
Adam Barth 已提交
83
  _platformView = std::make_unique<shell::PlatformViewIOS>(
84 85
      reinterpret_cast<CAEAGLLayer*>(self.view.layer));
  _platformView->SetupResourceContextOnIOThread();
C
Chinmay Garde 已提交
86

87 88 89
  _platformPlugin.reset([[FlutterPlatformPlugin alloc] init]);
  [self addMessageListener:_platformPlugin.get()];

90
  [self setupNotificationCenterObservers];
C
Chinmay Garde 已提交
91

92
  [self connectToEngineAndLoad];
C
Chinmay Garde 已提交
93 94
}

95 96 97 98
- (void)setupNotificationCenterObservers {
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onOrientationPreferencesUpdated:)
A
Adam Barth 已提交
99
                 name:@(shell::kOrientationUpdateNotificationName)
100 101
               object:nil];

102 103
  [center addObserver:self
             selector:@selector(onPreferredStatusBarStyleUpdated:)
A
Adam Barth 已提交
104
                 name:@(shell::kOverlayStyleUpdateNotificationName)
105 106
               object:nil];

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
  [center addObserver:self
             selector:@selector(applicationBecameActive:)
                 name:UIApplicationDidBecomeActiveNotification
               object:nil];

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

  [center addObserver:self
             selector:@selector(keyboardWasShown:)
                 name:UIKeyboardDidShowNotification
               object:nil];

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

  [center addObserver:self
             selector:@selector(onLocaleUpdated:)
                 name:NSCurrentLocaleDidChangeNotification
               object:nil];
131 132 133 134 135

  [center addObserver:self
             selector:@selector(onVoiceOverChanged:)
                 name:UIAccessibilityVoiceOverStatusChanged
               object:nil];
C
Chinmay Garde 已提交
136 137
}

138
#pragma mark - Initializing the engine
139

140
- (void)alertView:(UIAlertView*)alertView
141
    clickedButtonAtIndex:(NSInteger)buttonIndex {
142 143 144
  exit(0);
}

145
- (void)connectToEngineAndLoad {
C
Chinmay Garde 已提交
146
  TRACE_EVENT0("flutter", "connectToEngineAndLoad");
147

148
  _platformView->ConnectToEngineAndSetupServices();
149

150
  // We ask the VM to check what it supports.
151 152
  const enum VMType type =
      Dart_IsPrecompiledRuntime() ? VMTypePrecompilation : VMTypeInterpreter;
153

154
  [_dartProject launchInEngine:_platformView->engineProxy()
155 156 157 158 159 160
                embedderVMType:type
                        result:^(BOOL success, NSString* message) {
                          if (!success) {
                            UIAlertView* alert = [[UIAlertView alloc]
                                    initWithTitle:@"Launch Error"
                                          message:message
161
                                         delegate:self
162 163 164 165 166 167
                                cancelButtonTitle:@"OK"
                                otherButtonTitles:nil];
                            [alert show];
                            [alert release];
                          }
                        }];
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
}

#pragma mark - Loading the view

- (void)loadView {
  FlutterView* surface = [[FlutterView alloc] init];

  self.view = surface;
  self.view.multipleTouchEnabled = YES;
  self.view.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

  [surface release];
}

#pragma mark - Application lifecycle notifications

- (void)applicationBecameActive:(NSNotification*)notification {
186 187 188
  auto& engine = _platformView->engineProxy();
  if (engine) {
    engine->OnAppLifecycleStateChanged(sky::AppLifecycleState::RESUMED);
189 190 191 192
  }
}

- (void)applicationWillResignActive:(NSNotification*)notification {
193 194 195
  auto& engine = _platformView->engineProxy();
  if (engine) {
    engine->OnAppLifecycleStateChanged(sky::AppLifecycleState::PAUSED);
196 197 198 199 200 201 202 203 204 205 206
  }
}

#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

207 208
using PointerChangeMapperPhase =
    std::pair<blink::PointerData::Change, MapperPhase>;
A
Adam Barth 已提交
209
static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(
210 211 212
    UITouchPhase phase) {
  switch (phase) {
    case UITouchPhaseBegan:
A
Adam Barth 已提交
213 214
      return PointerChangeMapperPhase(blink::PointerData::Change::kDown,
                                      MapperPhase::Added);
215 216 217 218
    case UITouchPhaseMoved:
    case UITouchPhaseStationary:
      // There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type
      // with the same coordinates
A
Adam Barth 已提交
219 220
      return PointerChangeMapperPhase(blink::PointerData::Change::kMove,
                                      MapperPhase::Accessed);
221
    case UITouchPhaseEnded:
A
Adam Barth 已提交
222 223
      return PointerChangeMapperPhase(blink::PointerData::Change::kUp,
                                      MapperPhase::Removed);
224
    case UITouchPhaseCancelled:
A
Adam Barth 已提交
225 226
      return PointerChangeMapperPhase(blink::PointerData::Change::kCancel,
                                      MapperPhase::Removed);
227 228
  }

A
Adam Barth 已提交
229 230
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel,
                                  MapperPhase::Accessed);
231
}
C
Chinmay Garde 已提交
232 233

- (void)dispatchTouches:(NSSet*)touches phase:(UITouchPhase)phase {
A
Adam Barth 已提交
234
  auto eventTypePhase = PointerChangePhaseFromUITouchPhase(phase);
C
Chinmay Garde 已提交
235
  const CGFloat scale = [UIScreen mainScreen].scale;
A
Adam Barth 已提交
236
  auto packet = std::make_unique<blink::PointerDataPacket>(touches.count);
C
Chinmay Garde 已提交
237

A
Adam Barth 已提交
238
  int i = 0;
C
Chinmay Garde 已提交
239
  for (UITouch* touch in touches) {
240 241 242 243
    int touch_identifier = 0;

    switch (eventTypePhase.second) {
      case Accessed:
244
        touch_identifier = _touchMapper.identifierOf(touch);
245 246
        break;
      case Added:
247
        touch_identifier = _touchMapper.registerTouch(touch);
248 249
        break;
      case Removed:
250
        touch_identifier = _touchMapper.unregisterTouch(touch);
251 252
        break;
    }
253

254
    DCHECK(touch_identifier != 0);
C
Chinmay Garde 已提交
255
    CGPoint windowCoordinates = [touch locationInView:nil];
256 257

    auto pointer_time =
A
Adam Barth 已提交
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
        ftl::TimeDelta::FromSeconds(touch.timestamp).ToMicroseconds();

    blink::PointerData pointer_data;
    pointer_data.Clear();

    pointer_data.time_stamp = pointer_time;
    pointer_data.change = eventTypePhase.first;
    pointer_data.kind = blink::PointerData::DeviceKind::kTouch;
    pointer_data.pointer = touch_identifier;
    pointer_data.physical_x = windowCoordinates.x * scale;
    pointer_data.physical_y = windowCoordinates.y * scale;
    pointer_data.pressure = 1.0;
    pointer_data.pressure_max = 1.0;

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

A
Adam Barth 已提交
275
  blink::Threads::UI()->PostTask(ftl::MakeCopyable([
276 277 278 279 280
    engine = _platformView->engine().GetWeakPtr(), packet = std::move(packet)
  ] {
    if (engine.get())
      engine->DispatchPointerDataPacket(*packet);
  }));
C
Chinmay Garde 已提交
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
}

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

299
#pragma mark - Handle view resizing
300

301 302 303 304 305 306 307 308 309 310
- (void)viewDidLayoutSubviews {
  CGSize size = self.view.bounds.size;
  CGFloat scale = [UIScreen mainScreen].scale;

  _viewportMetrics->device_pixel_ratio = scale;
  _viewportMetrics->physical_width = size.width * scale;
  _viewportMetrics->physical_height = size.height * scale;
  _viewportMetrics->physical_padding_top =
      [UIApplication sharedApplication].statusBarFrame.size.height * scale;

311 312
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
313 314
}

315
#pragma mark - Keyboard events
316

317 318 319 320 321 322
- (void)keyboardWasShown:(NSNotification*)notification {
  NSDictionary* info = [notification userInfo];
  CGFloat bottom = CGRectGetHeight(
      [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]);
  CGFloat scale = [UIScreen mainScreen].scale;
  _viewportMetrics->physical_padding_bottom = bottom * scale;
323 324
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
325 326
}

327 328
- (void)keyboardWillBeHidden:(NSNotification*)notification {
  _viewportMetrics->physical_padding_bottom = 0.0;
329 330
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
331 332
}

333 334 335 336 337 338 339 340
#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;

    NSNumber* update =
A
Adam Barth 已提交
341
        info[@(shell::kOrientationUpdateNotificationKey)];
342 343 344 345 346 347 348 349 350 351 352 353

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
354 355
}

356 357
- (BOOL)shouldAutorotate {
  return YES;
358 359
}

360 361 362 363
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

364 365 366 367 368 369 370
#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.
371
  bool enabled = true;
372
#else
373
  bool enabled = UIAccessibilityIsVoiceOverRunning();
374
#endif
375
  _platformView->ToggleAccessibility(self.view, enabled);
376 377
}

378 379 380 381 382 383
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
  NSLocale* currentLocale = [NSLocale currentLocale];
  NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
  NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
384 385
  _platformView->engineProxy()->OnLocaleChanged(languageCode.UTF8String,
                                                countryCode.UTF8String);
386 387 388 389 390
}

#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
391
  CHECK(_platformView != nullptr);
392 393

  if (appeared) {
394 395
    _platformView->NotifyCreated(
        std::make_unique<shell::GPUSurfaceGL>(_platformView.get()));
396
  } else {
397
    _platformView->NotifyDestroyed();
398 399 400
  }
}

401 402
- (void)viewDidAppear:(BOOL)animated {
  [self surfaceUpdated:YES];
403 404
  [self onLocaleUpdated:nil];
  [self onVoiceOverChanged:nil];
405 406

  [super viewWillAppear:animated];
407
}
C
Chinmay Garde 已提交
408

409 410
- (void)viewWillDisappear:(BOOL)animated {
  [self surfaceUpdated:NO];
411 412

  [super viewWillDisappear:animated];
413 414
}

C
Chinmay Garde 已提交
415
- (void)dealloc {
416
  [[NSNotificationCenter defaultCenter] removeObserver:self];
417 418
  [super dealloc];
}
419

420 421 422 423 424 425 426 427 428 429 430 431
#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;

    NSNumber* update =
A
Adam Barth 已提交
432
        info[@(shell::kOverlayStyleUpdateNotificationKey)];
433 434 435 436 437 438 439 440 441 442 443 444 445 446

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

447
#pragma mark - Application Messages
448

449
- (void)sendString:(NSString*)message withMessageName:(NSString*)messageName {
450 451
  NSAssert(message, @"The message must not be null");
  NSAssert(messageName, @"The messageName must not be null");
452 453 454
  _platformView->AppMessageSender()->SendString(
      messageName.UTF8String, message.UTF8String,
      [](const mojo::String& response) {});
455 456 457
}

- (void)sendString:(NSString*)message
458 459
    withMessageName:(NSString*)messageName
           callback:(void (^)(NSString*))callback {
460 461 462
  NSAssert(message, @"The message must not be null");
  NSAssert(messageName, @"The messageName must not be null");
  NSAssert(callback, @"The callback must not be null");
463
  base::mac::ScopedBlock<void (^)(NSString*)> callback_ptr(
464
      callback, base::scoped_policy::RETAIN);
465
  _platformView->AppMessageSender()->SendString(
466
      messageName.UTF8String, message.UTF8String,
467
      [callback_ptr](const mojo::String& response) {
468 469
        callback_ptr.get()(base::SysUTF8ToNSString(response));
      });
470 471
}

472
- (void)addMessageListener:(NSObject<FlutterMessageListener>*)listener {
473
  NSAssert(listener, @"The listener must not be null");
474
  NSString* messageName = listener.messageName;
475
  NSAssert(messageName, @"The messageName must not be null");
476 477
  _platformView->AppMessageReceiver().SetMessageListener(messageName.UTF8String,
                                                         listener);
478 479
}

480
- (void)removeMessageListener:(NSObject<FlutterMessageListener>*)listener {
481
  NSAssert(listener, @"The listener must not be null");
482
  NSString* messageName = listener.messageName;
483
  NSAssert(messageName, @"The messageName must not be null");
484 485
  _platformView->AppMessageReceiver().SetMessageListener(messageName.UTF8String,
                                                         nil);
486 487
}

488 489
- (void)addAsyncMessageListener:
    (NSObject<FlutterAsyncMessageListener>*)listener {
490 491 492
  NSAssert(listener, @"The listener must not be null");
  NSString* messageName = listener.messageName;
  NSAssert(messageName, @"The messageName must not be null");
493 494
  _platformView->AppMessageReceiver().SetAsyncMessageListener(
      messageName.UTF8String, listener);
495 496
}

497 498
- (void)removeAsyncMessageListener:
    (NSObject<FlutterAsyncMessageListener>*)listener {
499 500 501
  NSAssert(listener, @"The listener must not be null");
  NSString* messageName = listener.messageName;
  NSAssert(messageName, @"The messageName must not be null");
502 503
  _platformView->AppMessageReceiver().SetAsyncMessageListener(
      messageName.UTF8String, nil);
C
Chinmay Garde 已提交
504 505 506
}

@end