Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 55 additions & 36 deletions shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,41 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration {

// Invalidate old vsync client if old animation is not completed.
[self invalidateKeyboardAnimationVSyncClient];
[self setupKeyboardAnimationVsyncClient];

FlutterKeyboardAnimationCallback keyboardAnimationCallback =
[weakSelf = [self getWeakPtr]](fml::TimePoint keyboardAnimationTargetTime) {
if (!weakSelf) {
return;
}
fml::scoped_nsobject<FlutterViewController> flutterViewController(
[(FlutterViewController*)weakSelf.get() retain]);
if (!flutterViewController) {
return;
}

if ([flutterViewController keyboardAnimationView].superview == nil) {
// Ensure the keyboardAnimationView is in view hierarchy when animation running.
[flutterViewController.get().view
addSubview:[flutterViewController keyboardAnimationView]];
}

if ([flutterViewController keyboardSpringAnimation] == nil) {
if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
flutterViewController.get()
.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
[flutterViewController updateViewportMetricsIfNeeded];
}
} else {
fml::TimeDelta timeElapsed =
keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime;
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
[[flutterViewController keyboardSpringAnimation]
curveFunction:timeElapsed.ToSecondsF()];
[flutterViewController updateViewportMetricsIfNeeded];
}
};
[self setupKeyboardAnimationVsyncClient:keyboardAnimationCallback];
VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;

[UIView animateWithDuration:duration
Expand Down Expand Up @@ -1639,45 +1673,30 @@ - (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
toValue:self.targetViewInsetBottom]);
}

- (void)setupKeyboardAnimationVsyncClient {
auto callback = [weakSelf =
[self getWeakPtr]](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
if (!weakSelf) {
return;
}
fml::scoped_nsobject<FlutterViewController> flutterViewController(
[(FlutterViewController*)weakSelf.get() retain]);
if (!flutterViewController) {
return;
}

if ([flutterViewController keyboardAnimationView].superview == nil) {
// Ensure the keyboardAnimationView is in view hierarchy when animation running.
[flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
}

if ([flutterViewController keyboardSpringAnimation] == nil) {
if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
flutterViewController.get()
.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
[flutterViewController updateViewportMetricsIfNeeded];
}
} else {
fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() -
flutterViewController.get().keyboardAnimationStartTime;

flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
[[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
[flutterViewController updateViewportMetricsIfNeeded];
}
};
- (void)setupKeyboardAnimationVsyncClient:
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback {
if (!keyboardAnimationCallback) {
return;
}
flutter::Shell& shell = [_engine.get() shell];
NSAssert(_keyboardAnimationVSyncClient == nil,
@"_keyboardAnimationVSyncClient must be nil when setup");

// Need to call the updateViewportMetrics signal after flutter UI thread's process callback,
// so here need to wait vsync on UI thread instead of posting the signal
// on platform thread directly.
auto uiCallback = [keyboardAnimationCallback,
&shell](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval;
shell.GetTaskRunners().GetPlatformTaskRunner()->PostTask(
[keyboardAnimationCallback, keyboardAnimationTargetTime] {
keyboardAnimationCallback(keyboardAnimationTargetTime);
});
};
_keyboardAnimationVSyncClient =
[[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner()
callback:callback];
[[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetUITaskRunner()
callback:uiCallback];
_keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
[_keyboardAnimationVSyncClient await];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import "flutter/lib/ui/window/platform_configuration.h"
#include "flutter/lib/ui/window/pointer_data.h"
#import "flutter/lib/ui/window/viewport_metrics.h"
#import "flutter/shell/common/shell.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
Expand All @@ -26,6 +27,7 @@ - (FlutterTextInputPlugin*)textInputPlugin;
- (void)sendKeyEvent:(const FlutterKeyEvent&)event
callback:(nullable FlutterKeyEventCallback)callback
userData:(nullable void*)userData;
- (flutter::Shell&)shell;
@end

/// Sometimes we have to use a custom mock to avoid retain cycles in OCMock.
Expand Down Expand Up @@ -135,10 +137,11 @@ - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification;
- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
- (void)setupKeyboardAnimationVsyncClient;
- (UIView*)keyboardAnimationView;
- (SpringAnimation*)keyboardSpringAnimation;
- (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
- (void)setupKeyboardAnimationVsyncClient:
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback;
- (void)ensureViewportMetricsIsCorrect;
- (void)invalidateKeyboardAnimationVSyncClient;
- (void)addInternalPlugins;
Expand Down Expand Up @@ -197,18 +200,6 @@ - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
}

- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationVsyncClient {
FlutterEngine* engine = [[FlutterEngine alloc] init];
[engine runWithEntrypoint:nil];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
viewControllerMock.targetViewInsetBottom = 100;
[viewControllerMock startKeyBoardAnimation:0.25];
OCMVerify([viewControllerMock setupKeyboardAnimationVsyncClient]);
}

- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
FlutterEngine* engine = [[FlutterEngine alloc] init];
[engine runWithEntrypoint:nil];
Expand Down Expand Up @@ -451,6 +442,29 @@ - (void)testShouldIgnoreKeyboardNotification {
}
}

- (void)testKeyboardAnimationWillWaitUIThreadVsync {
FlutterEngine* engine = [[FlutterEngine alloc] init];
[engine runWithEntrypoint:nil];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
// Post a task to UI thread to block the thread.
flutter::Shell& shell = [engine shell];
const int delayTime = 1;
shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); });
XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"];
CFTimeInterval startTime = CACurrentMediaTime();
CFTimeInterval fulfillTime;
FlutterKeyboardAnimationCallback callback = [&expectation,
&fulfillTime](fml::TimePoint targetTime) {
fulfillTime = CACurrentMediaTime();
[expectation fulfill];
};
[viewController setupKeyboardAnimationVsyncClient:callback];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
XCTAssertTrue(fulfillTime - startTime > delayTime);
}

- (void)testCalculateKeyboardAttachMode {
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"

FLUTTER_ASSERT_NOT_ARC
Expand All @@ -31,7 +32,9 @@ @interface FlutterViewController (Testing)
@property(nonatomic, retain) VSyncClient* touchRateCorrectionVSyncClient;

- (void)createTouchRateCorrectionVSyncClientIfNeeded;
- (void)setupKeyboardAnimationVsyncClient;
- (void)setupKeyboardAnimationVsyncClient:
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback;
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;

@end
Expand All @@ -53,7 +56,8 @@ - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterV
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
[viewController setupKeyboardAnimationVsyncClient];
FlutterKeyboardAnimationCallback callback = [](fml::TimePoint targetTime) {};
[viewController setupKeyboardAnimationVsyncClient:callback];
XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
XCTAssertNotNil(link);
Expand Down Expand Up @@ -173,4 +177,26 @@ - (void)testTriggerTouchRateCorrectionVSyncClientCorrectly {
XCTAssertFalse(link.isPaused);
}

- (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
FlutterEngine* engine = [[FlutterEngine alloc] init];
[engine runWithEntrypoint:nil];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
viewController.targetViewInsetBottom = 100;
[viewController startKeyBoardAnimation:0.25];
XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
}

- (void)
testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
FlutterEngine* engine = [[FlutterEngine alloc] init];
[engine runWithEntrypoint:nil];
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
[viewController setupKeyboardAnimationVsyncClient:nil];
XCTAssertNil(viewController.keyboardAnimationVSyncClient);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ typedef NS_ENUM(NSInteger, FlutterKeyboardMode) {
FlutterKeyboardModeFloating = 2,
};

typedef std::function<void(fml::TimePoint)> FlutterKeyboardAnimationCallback;

@interface FlutterViewController () <FlutterViewResponder>

@property(class, nonatomic, readonly) BOOL accessibilityIsOnOffSwitchLabelsEnabled;
Expand Down