FlutterViewController.mm 18.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"
16
#include "flutter/shell/platform/darwin/common/string_conversions.h"
A
Adam Barth 已提交
17
#include "flutter/shell/platform/darwin/ios/framework/Source/flutter_touch_mapper.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/platform_view_ios.h"
A
Adam Barth 已提交
23
#include "lib/ftl/functional/make_copyable.h"
24
#include "lib/ftl/time/time_delta.h"
25

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
namespace {

typedef void (^PlatformMessageResponseCallback)(NSString*);

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

 public:
  void Complete(std::vector<uint8_t> data) override {
    ftl::RefPtr<PlatformMessageResponseDarwin> self(this);
    blink::Threads::Platform()->PostTask(
        ftl::MakeCopyable([ self, data = std::move(data) ]() mutable {
          self->callback_.get()(shell::GetNSStringFromVector(data));
        }));
  }

  void CompleteWithError() override { Complete(std::vector<uint8_t>()); }

 private:
  explicit PlatformMessageResponseDarwin(PlatformMessageResponseCallback callback)
      : callback_(callback, base::scoped_policy::RETAIN) {}

  base::mac::ScopedBlock<PlatformMessageResponseCallback> callback_;
};

}  // namespace

53
@interface FlutterViewController ()<UIAlertViewDelegate, FlutterTextInputDelegate>
54 55
@end

56 57 58
void FlutterInit(int argc, const char* argv[]) {
  NSBundle* bundle = [NSBundle bundleForClass:[FlutterViewController class]];
  NSString* icuDataPath = [bundle pathForResource:@"icudtl" ofType:@"dat"];
59
  shell::PlatformMacMain(argc, argv, icuDataPath.UTF8String);
60 61
}

62
@implementation FlutterViewController {
63
  base::scoped_nsprotocol<FlutterDartProject*> _dartProject;
64
  UIInterfaceOrientationMask _orientationPreferences;
65
  UIStatusBarStyle _statusBarStyle;
66
  sky::ViewportMetricsPtr _viewportMetrics;
67 68
  shell::TouchMapper _touchMapper;
  std::unique_ptr<shell::PlatformViewIOS> _platformView;
69
  base::scoped_nsprotocol<FlutterPlatformPlugin*> _platformPlugin;
70
  base::scoped_nsprotocol<FlutterTextInputPlugin*> _textInputPlugin;
71

72
  BOOL _initialized;
73 74
}

75 76
#pragma mark - Manage and override all designated initializers

77 78 79 80
- (instancetype)initWithProject:(FlutterDartProject*)project
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
81

82
  if (self) {
83
    if (project == nil)
84 85
      _dartProject.reset(
          [[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]);
86 87
    else
      _dartProject.reset([project retain]);
88 89

    [self performCommonViewControllerInitialization];
90
  }
91

92
  return self;
C
Chinmay Garde 已提交
93 94
}

95 96
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
97
  return [self initWithProject:nil nibName:nil bundle:nil];
C
Chinmay Garde 已提交
98 99
}

100
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
101
  return [self initWithProject:nil nibName:nil bundle:nil];
102 103
}

104
#pragma mark - Common view controller initialization tasks
C
Chinmay Garde 已提交
105

106
- (void)performCommonViewControllerInitialization {
107
  if (_initialized)
108 109
    return;
  _initialized = YES;
C
Chinmay Garde 已提交
110

111
  _orientationPreferences = UIInterfaceOrientationMaskAll;
112
  _statusBarStyle = UIStatusBarStyleDefault;
113
  _viewportMetrics = sky::ViewportMetrics::New();
A
Adam Barth 已提交
114
  _platformView = std::make_unique<shell::PlatformViewIOS>(
115 116
      reinterpret_cast<CAEAGLLayer*>(self.view.layer));
  _platformView->SetupResourceContextOnIOThread();
C
Chinmay Garde 已提交
117

118 119 120
  _platformPlugin.reset([[FlutterPlatformPlugin alloc] init]);
  [self addMessageListener:_platformPlugin.get()];

121 122 123 124 125

  _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
  _textInputPlugin.get().textInputDelegate = self;
  [self addMessageListener:_textInputPlugin.get()];

126
  [self setupNotificationCenterObservers];
C
Chinmay Garde 已提交
127

128
  [self connectToEngineAndLoad];
C
Chinmay Garde 已提交
129 130
}

131 132 133 134
- (void)setupNotificationCenterObservers {
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onOrientationPreferencesUpdated:)
A
Adam Barth 已提交
135
                 name:@(shell::kOrientationUpdateNotificationName)
136 137
               object:nil];

138 139
  [center addObserver:self
             selector:@selector(onPreferredStatusBarStyleUpdated:)
A
Adam Barth 已提交
140
                 name:@(shell::kOverlayStyleUpdateNotificationName)
141 142
               object:nil];

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
  [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];
167 168 169 170 171

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

174
#pragma mark - Initializing the engine
175

176
- (void)alertView:(UIAlertView*)alertView
177
    clickedButtonAtIndex:(NSInteger)buttonIndex {
178 179 180
  exit(0);
}

181
- (void)connectToEngineAndLoad {
C
Chinmay Garde 已提交
182
  TRACE_EVENT0("flutter", "connectToEngineAndLoad");
183

184
  _platformView->ConnectToEngineAndSetupServices();
185

186
  // We ask the VM to check what it supports.
187 188
  const enum VMType type =
      Dart_IsPrecompiledRuntime() ? VMTypePrecompilation : VMTypeInterpreter;
189

190
  [_dartProject launchInEngine:_platformView->engineProxy()
191 192 193 194 195 196
                embedderVMType:type
                        result:^(BOOL success, NSString* message) {
                          if (!success) {
                            UIAlertView* alert = [[UIAlertView alloc]
                                    initWithTitle:@"Launch Error"
                                          message:message
197
                                         delegate:self
198 199 200 201 202 203
                                cancelButtonTitle:@"OK"
                                otherButtonTitles:nil];
                            [alert show];
                            [alert release];
                          }
                        }];
204 205 206 207 208
}

#pragma mark - Loading the view

- (void)loadView {
209
  FlutterView* view = [[FlutterView alloc] init];
210

211
  self.view = view;
212 213 214 215
  self.view.multipleTouchEnabled = YES;
  self.view.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

216
  [view release];
217 218 219 220 221
}

#pragma mark - Application lifecycle notifications

- (void)applicationBecameActive:(NSNotification*)notification {
222 223 224
  auto& engine = _platformView->engineProxy();
  if (engine) {
    engine->OnAppLifecycleStateChanged(sky::AppLifecycleState::RESUMED);
225 226 227 228
  }
}

- (void)applicationWillResignActive:(NSNotification*)notification {
229 230 231
  auto& engine = _platformView->engineProxy();
  if (engine) {
    engine->OnAppLifecycleStateChanged(sky::AppLifecycleState::PAUSED);
232 233 234 235 236 237 238 239 240 241 242
  }
}

#pragma mark - Touch event handling

enum MapperPhase {
  Accessed,
  Added,
  Removed,
};

243 244
using PointerChangeMapperPhase =
    std::pair<blink::PointerData::Change, MapperPhase>;
A
Adam Barth 已提交
245
static inline PointerChangeMapperPhase PointerChangePhaseFromUITouchPhase(
246 247 248
    UITouchPhase phase) {
  switch (phase) {
    case UITouchPhaseBegan:
A
Adam Barth 已提交
249 250
      return PointerChangeMapperPhase(blink::PointerData::Change::kDown,
                                      MapperPhase::Added);
251 252 253 254
    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 已提交
255 256
      return PointerChangeMapperPhase(blink::PointerData::Change::kMove,
                                      MapperPhase::Accessed);
257
    case UITouchPhaseEnded:
A
Adam Barth 已提交
258 259
      return PointerChangeMapperPhase(blink::PointerData::Change::kUp,
                                      MapperPhase::Removed);
260
    case UITouchPhaseCancelled:
A
Adam Barth 已提交
261 262
      return PointerChangeMapperPhase(blink::PointerData::Change::kCancel,
                                      MapperPhase::Removed);
263 264
  }

A
Adam Barth 已提交
265 266
  return PointerChangeMapperPhase(blink::PointerData::Change::kCancel,
                                  MapperPhase::Accessed);
267
}
C
Chinmay Garde 已提交
268 269

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

A
Adam Barth 已提交
274
  int i = 0;
C
Chinmay Garde 已提交
275
  for (UITouch* touch in touches) {
276 277 278 279
    int touch_identifier = 0;

    switch (eventTypePhase.second) {
      case Accessed:
280
        touch_identifier = _touchMapper.identifierOf(touch);
281 282
        break;
      case Added:
283
        touch_identifier = _touchMapper.registerTouch(touch);
284 285
        break;
      case Removed:
286
        touch_identifier = _touchMapper.unregisterTouch(touch);
287 288
        break;
    }
289

290
    DCHECK(touch_identifier != 0);
C
Chinmay Garde 已提交
291
    CGPoint windowCoordinates = [touch locationInView:nil];
292

A
Adam Barth 已提交
293 294 295
    blink::PointerData pointer_data;
    pointer_data.Clear();

296 297
    constexpr int kMicrosecondsPerSecond = 1000 * 1000;
    pointer_data.time_stamp = touch.timestamp * kMicrosecondsPerSecond;
A
Adam Barth 已提交
298 299 300 301 302 303 304 305 306
    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 已提交
307
  }
308

A
Adam Barth 已提交
309
  blink::Threads::UI()->PostTask(ftl::MakeCopyable([
310 311 312 313 314
    engine = _platformView->engine().GetWeakPtr(), packet = std::move(packet)
  ] {
    if (engine.get())
      engine->DispatchPointerDataPacket(*packet);
  }));
C
Chinmay Garde 已提交
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
}

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

333
#pragma mark - Handle view resizing
334

335 336 337 338 339 340 341 342 343 344
- (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;

345 346
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
347 348
}

349
#pragma mark - Keyboard events
350

351 352 353 354 355 356
- (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;
357 358
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
359 360
}

361 362
- (void)keyboardWillBeHidden:(NSNotification*)notification {
  _viewportMetrics->physical_padding_bottom = 0.0;
363 364
  _platformView->engineProxy()->OnViewportMetricsChanged(
      _viewportMetrics.Clone());
365 366
}

367 368 369 370 371 372 373 374 375
#pragma mark - Text input delegate

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
  [self sendJSON:@{
    @"method": @"TextInputClient.updateEditingState",
    @"args": @[@(client), state],
  } withMessageName:@"flutter/textinputclient"];
}

376 377 378 379 380 381 382 383
#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 已提交
384
        info[@(shell::kOrientationUpdateNotificationKey)];
385 386 387 388 389 390 391 392 393 394 395 396

    if (update == nil) {
      return;
    }

    NSUInteger new_preferences = update.unsignedIntegerValue;

    if (new_preferences != _orientationPreferences) {
      _orientationPreferences = new_preferences;
      [UIViewController attemptRotationToDeviceOrientation];
    }
  });
397 398
}

399 400
- (BOOL)shouldAutorotate {
  return YES;
401 402
}

403 404 405 406
- (NSUInteger)supportedInterfaceOrientations {
  return _orientationPreferences;
}

407 408 409 410 411 412 413
#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.
414
  bool enabled = true;
415
#else
416
  bool enabled = UIAccessibilityIsVoiceOverRunning();
417
#endif
418
  _platformView->ToggleAccessibility(self.view, enabled);
419 420
}

421 422 423 424 425 426
#pragma mark - Locale updates

- (void)onLocaleUpdated:(NSNotification*)notification {
  NSLocale* currentLocale = [NSLocale currentLocale];
  NSString* languageCode = [currentLocale objectForKey:NSLocaleLanguageCode];
  NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
427 428
  _platformView->engineProxy()->OnLocaleChanged(languageCode.UTF8String,
                                                countryCode.UTF8String);
429 430 431 432 433
}

#pragma mark - Surface creation and teardown updates

- (void)surfaceUpdated:(BOOL)appeared {
434
  CHECK(_platformView != nullptr);
435 436

  if (appeared) {
437 438
    _platformView->NotifyCreated(
        std::make_unique<shell::GPUSurfaceGL>(_platformView.get()));
439
  } else {
440
    _platformView->NotifyDestroyed();
441 442 443
  }
}

444 445
- (void)viewDidAppear:(BOOL)animated {
  [self surfaceUpdated:YES];
446 447
  [self onLocaleUpdated:nil];
  [self onVoiceOverChanged:nil];
448 449

  [super viewWillAppear:animated];
450
}
C
Chinmay Garde 已提交
451

452 453
- (void)viewWillDisappear:(BOOL)animated {
  [self surfaceUpdated:NO];
454 455

  [super viewWillDisappear:animated];
456 457
}

C
Chinmay Garde 已提交
458
- (void)dealloc {
459
  [[NSNotificationCenter defaultCenter] removeObserver:self];
460 461
  [super dealloc];
}
462

463 464 465 466 467 468 469 470 471 472 473 474
#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 已提交
475
        info[@(shell::kOverlayStyleUpdateNotificationKey)];
476 477 478 479 480 481 482 483 484 485 486 487 488 489

    if (update == nil) {
      return;
    }

    NSInteger style = update.integerValue;

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

490
#pragma mark - Application Messages
491

492
- (void)sendString:(NSString*)message withMessageName:(NSString*)channel {
493
  NSAssert(message, @"The message must not be null");
494 495 496 497 498 499
  NSAssert(channel, @"The channel must not be null");
  _platformView->DispatchPlatformMessage(
      ftl::MakeRefCounted<blink::PlatformMessage>(
          channel.UTF8String,
          shell::GetVectorFromNSString(message),
          nullptr));
500 501 502
}

- (void)sendString:(NSString*)message
503
    withMessageName:(NSString*)channel
504
           callback:(void (^)(NSString*))callback {
505
  NSAssert(message, @"The message must not be null");
506
  NSAssert(channel, @"The channel must not be null");
507
  NSAssert(callback, @"The callback must not be null");
508 509 510 511 512
  _platformView->DispatchPlatformMessage(
      ftl::MakeRefCounted<blink::PlatformMessage>(
          channel.UTF8String,
          shell::GetVectorFromNSString(message),
          ftl::MakeRefCounted<PlatformMessageResponseDarwin>(callback)));
513 514
}

515
- (void)sendJSON:(NSDictionary*)message withMessageName:(NSString*)channel {
516 517 518
  NSData* data = [NSJSONSerialization dataWithJSONObject:message options:0 error:nil];
  if (!data)
    return;
519
  const uint8_t* bytes = static_cast<const uint8_t*>(data.bytes);
520 521
  _platformView->DispatchPlatformMessage(
      ftl::MakeRefCounted<blink::PlatformMessage>(
522
          channel.UTF8String,
523
          std::vector<uint8_t>(bytes, bytes + data.length),
524 525 526
          nullptr));
}

527
- (void)addMessageListener:(NSObject<FlutterMessageListener>*)listener {
528
  NSAssert(listener, @"The listener must not be null");
529 530 531 532
  NSString* channel = listener.messageName;
  NSAssert(channel, @"The channel must not be null");
  _platformView->platform_message_router().SetMessageListener(channel.UTF8String,
                                                              listener);
533 534
}

535
- (void)removeMessageListener:(NSObject<FlutterMessageListener>*)listener {
536
  NSAssert(listener, @"The listener must not be null");
537 538 539 540
  NSString* channel = listener.messageName;
  NSAssert(channel, @"The channel must not be null");
  _platformView->platform_message_router().SetMessageListener(channel.UTF8String,
                                                              nil);
541 542
}

543 544
- (void)addAsyncMessageListener:
    (NSObject<FlutterAsyncMessageListener>*)listener {
545 546 547
  NSAssert(listener, @"The listener must not be null");
  NSString* messageName = listener.messageName;
  NSAssert(messageName, @"The messageName must not be null");
548
  _platformView->platform_message_router().SetAsyncMessageListener(
549
      messageName.UTF8String, listener);
550 551
}

552 553
- (void)removeAsyncMessageListener:
    (NSObject<FlutterAsyncMessageListener>*)listener {
554 555 556
  NSAssert(listener, @"The listener must not be null");
  NSString* messageName = listener.messageName;
  NSAssert(messageName, @"The messageName must not be null");
557
  _platformView->platform_message_router().SetAsyncMessageListener(
558
      messageName.UTF8String, nil);
C
Chinmay Garde 已提交
559 560 561
}

@end