| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h" |
| #include "flutter/fml/memory/weak_ptr.h" |
| #include "flutter/fml/platform/darwin/message_loop_darwin.h" |
| |
| static constexpr CFTimeInterval kDistantFuture = 1.0e10; |
| |
| @interface FlutterKeyboardManager () |
| |
| /** |
| * The primary responders added by addPrimaryResponder. |
| */ |
| @property(nonatomic, retain, readonly) |
| NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders; |
| |
| /** |
| * The secondary responders added by addSecondaryResponder. |
| */ |
| @property(nonatomic, retain, readonly) |
| NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders; |
| |
| - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press |
| complete:(nonnull KeyEventCompleteCallback)callback |
| API_AVAILABLE(ios(13.4)); |
| |
| @end |
| |
| @implementation FlutterKeyboardManager { |
| std::unique_ptr<fml::WeakPtrFactory<FlutterKeyboardManager>> _weakFactory; |
| } |
| |
| - (nonnull instancetype)init { |
| self = [super init]; |
| if (self != nil) { |
| _primaryResponders = [[NSMutableArray alloc] init]; |
| _secondaryResponders = [[NSMutableArray alloc] init]; |
| _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterKeyboardManager>>(self); |
| } |
| return self; |
| } |
| |
| - (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder { |
| [_primaryResponders addObject:responder]; |
| } |
| |
| - (void)addSecondaryResponder:(nonnull id<FlutterKeySecondaryResponder>)responder { |
| [_secondaryResponders addObject:responder]; |
| } |
| |
| - (void)dealloc { |
| // It will be destroyed and invalidate its weak pointers |
| // before any other members are destroyed. |
| _weakFactory.reset(); |
| |
| [_primaryResponders removeAllObjects]; |
| [_secondaryResponders removeAllObjects]; |
| [_primaryResponders release]; |
| [_secondaryResponders release]; |
| _primaryResponders = nil; |
| _secondaryResponders = nil; |
| [super dealloc]; |
| } |
| |
| - (fml::WeakPtr<FlutterKeyboardManager>)getWeakPtr { |
| return _weakFactory->GetWeakPtr(); |
| } |
| |
| - (void)handlePress:(nonnull FlutterUIPressProxy*)press |
| nextAction:(nonnull void (^)())next API_AVAILABLE(ios(13.4)) { |
| if (@available(iOS 13.4, *)) { |
| // no-op |
| } else { |
| return; |
| } |
| |
| bool __block wasHandled = false; |
| KeyEventCompleteCallback completeCallback = ^void(bool handled, FlutterUIPressProxy* press) { |
| wasHandled = handled; |
| CFRunLoopStop(CFRunLoopGetCurrent()); |
| }; |
| switch (press.phase) { |
| case UIPressPhaseBegan: |
| case UIPressPhaseEnded: { |
| // Having no primary responders requires extra logic, but Flutter hard-codes |
| // all primary responders, so this is a situation that Flutter will never |
| // encounter. |
| NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added."); |
| |
| __block auto weakSelf = [self getWeakPtr]; |
| __block NSUInteger unreplied = [self.primaryResponders count]; |
| __block BOOL anyHandled = false; |
| FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) { |
| unreplied--; |
| NSAssert(unreplied >= 0, @"More primary responders replied than expected."); |
| anyHandled = anyHandled || handled; |
| if (unreplied == 0) { |
| if (!anyHandled && weakSelf) { |
| [weakSelf.get() dispatchToSecondaryResponders:press complete:completeCallback]; |
| } else { |
| completeCallback(true, press); |
| } |
| } |
| }; |
| for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) { |
| [responder handlePress:press callback:replyCallback]; |
| } |
| // Create a nested run loop while we wait for a response from the |
| // framework. Once the completeCallback is called, this run loop will exit |
| // and the main one will resume. The completeCallback MUST be called, or |
| // the app will get stuck in this run loop indefinitely. |
| // |
| // We need to run in this mode so that UIKit doesn't give us new |
| // events until we are done processing this one. |
| CFRunLoopRunInMode(fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, kDistantFuture, NO); |
| break; |
| } |
| case UIPressPhaseChanged: |
| case UIPressPhaseCancelled: |
| case UIPressPhaseStationary: |
| break; |
| } |
| if (!wasHandled) { |
| next(); |
| } |
| } |
| |
| #pragma mark - Private |
| |
| - (void)dispatchToSecondaryResponders:(nonnull FlutterUIPressProxy*)press |
| complete:(nonnull KeyEventCompleteCallback)callback |
| API_AVAILABLE(ios(13.4)) { |
| if (@available(iOS 13.4, *)) { |
| // no-op |
| } else { |
| callback(false, press); |
| return; |
| } |
| |
| for (id<FlutterKeySecondaryResponder> responder in _secondaryResponders) { |
| if ([responder handlePress:press]) { |
| callback(true, press); |
| return; |
| } |
| } |
| callback(false, press); |
| } |
| |
| @end |