Flutter iOS Interactive Keyboard: Fixing Animation Issue (#44514)

This PR addresses an issue with the animation of the keyboard. In iOS 16.0 a delay was included in UIView becomeFirstResponder where the areAnimationsEnabled boolean is no longer immediately read. In response to this issue a delay is added that allows for the animation to be properly disabled. 

Design Document:
https://docs.google.com/document/d/1-T7_0mSkXzPaWxveeypIzzzAdyo-EEuP5V84161foL4/edit?pli=1

Issues Address:
https://github.com/flutter/flutter/issues/57609

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
index c263b37..a4fb7a0 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
@@ -21,6 +21,10 @@
 // it is activated.
 static constexpr double kUITextInputAccessibilityEnablingDelaySeconds = 0.5;
 
+// A delay before reenabling the UIView areAnimationsEnabled to YES
+// in order for becomeFirstResponder to receive the proper value
+static const NSTimeInterval kKeyboardAnimationDelaySeconds = 0.1;
+
 // The "canonical" invalid CGRect, similar to CGRectNull, used to
 // indicate a CGRect involved in firstRectForRange calculation is
 // invalid. The specific value is chosen so that if firstRectForRange
@@ -2369,8 +2373,13 @@
 - (void)showKeyboardAndRemoveScreenshot {
   [UIView setAnimationsEnabled:NO];
   [_cachedFirstResponder becomeFirstResponder];
-  [UIView setAnimationsEnabled:YES];
-  [self dismissKeyboardScreenshot];
+  // UIKit does not immediately access the areAnimationsEnabled Boolean so a delay is needed before
+  // returned
+  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kKeyboardAnimationDelaySeconds * NSEC_PER_SEC),
+                 dispatch_get_main_queue(), ^{
+                   [UIView setAnimationsEnabled:YES];
+                   [self dismissKeyboardScreenshot];
+                 });
 }
 
 - (void)handlePointerMove:(CGFloat)pointerY {
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
index d341013..edc95d8 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
@@ -75,6 +75,8 @@
 - (UIView*)hostView;
 - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView;
 - (void)startLiveTextInput;
+- (void)showKeyboardAndRemoveScreenshot;
+
 @end
 
 @interface FlutterTextInputPluginTest : XCTestCase
@@ -2890,5 +2892,25 @@
                 }];
   textInputPlugin.cachedFirstResponder = nil;
 }
+- (void)testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsNotImmediatelyEnable {
+  [UIView setAnimationsEnabled:YES];
+  [textInputPlugin showKeyboardAndRemoveScreenshot];
+  XCTAssertFalse(
+      UIView.areAnimationsEnabled,
+      @"The animation should still be disabled following showKeyboardAndRemoveScreenshot");
+}
+
+- (void)testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsReenabledAfterDelay {
+  [UIView setAnimationsEnabled:YES];
+  [textInputPlugin showKeyboardAndRemoveScreenshot];
+
+  NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) {
+    // This will be enabled after a delay
+    return UIView.areAnimationsEnabled;
+  }];
+  XCTNSPredicateExpectation* expectation =
+      [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil];
+  [self waitForExpectations:@[ expectation ] timeout:10.0];
+}
 
 @end