From 8472d6de2c934f93751f116a53cec4c5e9d780c2 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 14 Nov 2022 15:20:05 -0600 Subject: [PATCH 01/53] first keyboard commit --- .../framework/Source/FlutterViewController.mm | 303 ++++++++++++++++-- 1 file changed, 280 insertions(+), 23 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 45e90e0c76544..66f1ae80fbb71 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -33,6 +33,69 @@ static constexpr int kMicrosecondsPerSecond = 1000 * 1000; static constexpr CGFloat kScrollViewContentSize = 2.0; +static NSArray* const kKeyboardStops = @[ + @0.0104644605808437, + @0.0378945710726348, + @0.0772929434500889, + @0.124734869398615, + @0.177165077399258, + @0.232230492534301, + @0.288142884918677, + @0.343566301349425, + @0.397525020517165, + @0.449328480839688, + @0.498510223492776, + @0.544778389928067, + @0.587975728583874, + @0.628047412666313, + @0.665015260793732, + @0.698957194203488, + @0.729990965893388, + @0.758261365048704, + @0.783930239883246, + @0.807168798214102, + @0.828151741567624, + @0.847052868629256, + @0.864041850126972, + @0.879281932061502, + @0.89292836947895, + @0.905127430324033, + @0.91601583965413, + @0.925720559754988, + @0.934358822413164, + @0.942038346548186, + @0.94885768823959, + @0.954906681442232, + @0.960266936818686, + @0.965012373505042, + @0.969209764577412, + @0.972919281759302, + @0.97619502871874, + @0.979085555325651, + @0.981634347620896, + @0.983880290109166, + @0.985858098428022, + @0.987598721546455, + @0.989129713475411, + @0.99047557508464, + @0.991658067059749, + @0.992696495336988, + @0.993607970550618, + @0.994407643142672, + @0.995108915836915, + @0.995723635183195, + @0.996262263847681, + @0.996734035268435, + @0.997147092222164, + @0.997508610762945, + @0.997824910901819, + @0.99810155530117, + @0.998343437162298, + @0.998554858390709, + @0.998739599032797, + @0.998900978890739, + @1 +]; static NSString* const kFlutterRestorationStateAppData = @"FlutterRestorationStateAppData"; @@ -100,6 +163,59 @@ - (void)addInternalPlugins; - (void)deregisterNotifications; @end +@interface KeyboardAnimationLayer : CALayer +@property(nonatomic) CGFloat keyboardAnimationViewInsetBottom; +@property(nonatomic, copy) void (^updateViewport)(CGFloat* inset); +@end + +@implementation KeyboardAnimationLayer +@dynamic keyboardAnimationViewInsetBottom; + +- (id)initWithLayer:(id)layer { + if ((self = [super initWithLayer:layer])) { + // Check if it's the right class before casting + if ([layer isKindOfClass:[KeyboardAnimationLayer class]]) { + // Copy the value of "myProperty" over from the other layer + self.updateViewport = ((KeyboardAnimationLayer*)layer).updateViewport; + } + } + return self; +} + ++ (BOOL)needsDisplayForKey:(NSString*)key { + if ([key isEqualToString:@"keyboardAnimationViewInsetBottom"]) + return YES; + + return [super needsDisplayForKey:key]; +} + +- (void)drawInContext:(CGContextRef)ctx { + CGFloat newViewInsetBottom = self.keyboardAnimationViewInsetBottom; + + if (self.updateViewport) { + self.updateViewport(&newViewInsetBottom); + } +} +@end + +@interface KeyboardAnimationView : UIView +@property(nonatomic) fml::TimePoint startTime; +@property(nonatomic) NSTimeInterval duration; +@property(nonatomic) CGFloat from; +@property(nonatomic) CGFloat to; +@property(nonatomic) double lastComplete; +@end + +@implementation KeyboardAnimationView ++ (Class)layerClass { + return [KeyboardAnimationLayer class]; +} + +- (void)drawRect:(CGRect)rect { +} + +@end + @implementation FlutterViewController { std::unique_ptr> _weakFactory; fml::scoped_nsobject _engine; @@ -121,7 +237,7 @@ @implementation FlutterViewController { // UIScrollView with height zero and a content offset so we can get those events. See also: // https://github.com/flutter/flutter/issues/35050 fml::scoped_nsobject _scrollView; - fml::scoped_nsobject _keyboardAnimationView; + fml::scoped_nsobject _keyboardAnimationView; MouseState _mouseState; // Timestamp after which a scroll inertia cancel event should be inferred. NSTimeInterval _scrollInertiaEventStartline; @@ -1196,6 +1312,8 @@ - (void)invalidateTouchRateCorrectionVSyncClient { #pragma mark - Handle view resizing - (void)updateViewportMetrics { + NSLog(@"updating viewport"); + if ([_engine.get() viewController] == self) { [_engine.get() updateViewportMetrics:_viewportMetrics]; } @@ -1211,6 +1329,11 @@ - (CGFloat)statusBarPadding { } - (void)viewDidLayoutSubviews { + // if ([self keyboardAnimationView].superview != nil) { + // NSLog(@"escaped subview"); + // return; + // } + CGRect viewBounds = self.view.bounds; CGFloat scale = [UIScreen mainScreen].scale; @@ -1227,6 +1350,8 @@ - (void)viewDidLayoutSubviews { [self updateViewportPadding]; [self updateViewportMetrics]; + NSLog(@"updating subviews %@", NSStringFromCGRect(viewBounds)); + // There is no guarantee that UIKit will layout subviews when the application is active. Creating // the surface when inactive will cause GPU accesses from the background. Only wait for the first // frame to render when the application is actually active. @@ -1256,6 +1381,7 @@ - (void)viewDidLayoutSubviews { - (void)viewSafeAreaInsetsDidChange { [self updateViewportPadding]; [self updateViewportMetrics]; + NSLog(@"updating safe area"); [super viewSafeAreaInsetsDidChange]; } @@ -1342,7 +1468,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // When call this method first time, // initialize the keyboardAnimationView to get animation interpolation during animation. if ([self keyboardAnimationView] == nil) { - UIView* keyboardAnimationView = [[UIView alloc] init]; + KeyboardAnimationView* keyboardAnimationView = [[KeyboardAnimationView alloc] init]; [keyboardAnimationView setHidden:YES]; _keyboardAnimationView.reset(keyboardAnimationView); } @@ -1356,28 +1482,122 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Set animation begin value. [self keyboardAnimationView].frame = - CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0); + CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 1, 1); // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; [self setupKeyboardAnimationVsyncClient]; - VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient; + // VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient; + + // ((KeyboardAnimationLayer *) [self keyboardAnimationView].layer).updateViewport = ^(CGFloat + // *inset) { + // fml::WeakPtr weakSelf = [self getWeakPtr]; + // if (!weakSelf) { + // return; + // } + // + // fml::scoped_nsobject + // flutterViewController([(FlutterViewController*)weakSelf.get() retain]); + // + // if (!flutterViewController) { + // return; + // } + // + //// double smoothedInset = roundf(*inset); + //// + //// if (abs(flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom - + ///smoothedInset) < 1) { / NSLog(@"<1"); / return; / } + // NSLog(@"drawing %f", *inset); + // + // flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = *inset; + // [flutterViewController updateViewportMetrics]; + // }; + + // double from = [self keyboardAnimationView].layer.presentationLayer.frame.origin.y; + // + // ((KeyboardAnimationLayer *) [self + // keyboardAnimationView].layer).keyboardAnimationViewInsetBottom = self.targetViewInsetBottom; + // [CATransaction begin]; + // CASpringAnimation *keyboardViewAnimation = [CASpringAnimation + // animationWithKeyPath:@"keyboardAnimationViewInsetBottom"]; keyboardViewAnimation.damping = + // 500; keyboardViewAnimation.stiffness = 1000; keyboardViewAnimation.mass = 3; + // keyboardViewAnimation.initialVelocity = 0; + // keyboardViewAnimation.duration = duration * 4; + //// keyboardViewAnimation.fromValue = [NSNumber numberWithFloat:from]; + // keyboardViewAnimation.fromValue = [NSNumber + // numberWithFloat:_viewportMetrics.physical_view_inset_bottom]; keyboardViewAnimation.toValue + // = [NSNumber numberWithFloat:self.targetViewInsetBottom]; if (@available(iOS 15, *)) { + // keyboardViewAnimation.preferredFrameRateRange = CAFrameRateRangeMake(120.0, 120.0, + // 120.0); + // } + // + // NSLog(@"from %f", _viewportMetrics.physical_view_inset_bottom); + // NSLog(@"to %f", self.targetViewInsetBottom); + // [CATransaction setCompletionBlock:^{ + //// if (_keyboardAnimationVSyncClient == currentVsyncClient) { + // // Indicates the vsync client captured by this block is the original one, which also + // // indicates the animation has not been interrupted from its beginning. Moreover, + // // indicates the animation is over and there is no more to execute. + //// [self invalidateKeyboardAnimationVSyncClient]; + //// [self removeKeyboardAnimationView]; + //// [self ensureViewportMetricsIsCorrect]; + //// } + // }]; + // + // [((KeyboardAnimationLayer *) [self keyboardAnimationView].layer) + // addAnimation:keyboardViewAnimation forKey:nil]; [CATransaction commit]; [UIView animateWithDuration:duration - animations:^{ - // Set end value. - [self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); - } - completion:^(BOOL finished) { - if (_keyboardAnimationVSyncClient == currentVsyncClient) { - // Indicates the vsync client captured by this block is the original one, which also - // indicates the animation has not been interrupted from its beginning. Moreover, - // indicates the animation is over and there is no more to execute. - [self invalidateKeyboardAnimationVSyncClient]; - [self removeKeyboardAnimationView]; - [self ensureViewportMetricsIsCorrect]; - } - }]; + delay:0 + options:UIViewAnimationOptionPreferredFramesPerSecond60 + animations:^{ + // Set DisplayLink tracking values + KeyboardAnimationView* keyboardView = + (KeyboardAnimationView*)[self keyboardAnimationView]; + double toY = self.targetViewInsetBottom; + double fromY = _viewportMetrics.physical_view_inset_bottom + + ((toY - _viewportMetrics.physical_view_inset_bottom) * .046); + + // _viewportMetrics.physical_view_inset_bottom = fromY; + // [self updateViewportMetrics]; + + // fromY += ((toY - fromY) * 0.0268); + fromY = _viewportMetrics.physical_view_inset_bottom; + + // NSTimeInterval delayInSeconds = 2; + // dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, + // (int64_t)(delayInSeconds * NSEC_PER_MSEC)); dispatch_after(popTime, + // dispatch_get_main_queue(), ^(void){ + // NSLog(@"delayed"); + // _viewportMetrics.physical_view_inset_bottom = fromY; + // [self updateViewportMetrics]; + // }); + + // keyboardView.startTime = fml::TimePoint();//fml::TimePoint().Now(); + keyboardView.startTime = fml::TimePoint().Now(); + keyboardView.duration = duration; + keyboardView.from = fromY; + keyboardView.to = toY; + + // Set end value. + [self keyboardAnimationView].frame = + CGRectMake(0, self.targetViewInsetBottom, 0, 0); + } + completion:^(BOOL finished){ + // if (_keyboardAnimationVSyncClient == currentVsyncClient) { + // // Indicates the vsync client captured by this block is the original + // one, which also + // // indicates the animation has not been interrupted from its beginning. + // Moreover, + // // indicates the animation is over and there is no more to execute. + // KeyboardAnimationView* keyboardView = (KeyboardAnimationView *) [self + // keyboardAnimationView]; + // keyboardView.startTime = fml::TimePoint(); + // [self invalidateKeyboardAnimationVSyncClient]; + // [self removeKeyboardAnimationView]; + // [self ensureViewportMetricsIsCorrect]; + // } + }]; } - (void)setupKeyboardAnimationVsyncClient { @@ -1396,12 +1616,48 @@ - (void)setupKeyboardAnimationVsyncClient { // Ensure the keyboardAnimationView is in view hierarchy when animation running. [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; } - if ([flutterViewController keyboardAnimationView].layer.presentationLayer) { - CGFloat value = - [flutterViewController keyboardAnimationView].layer.presentationLayer.frame.origin.y; - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = value; - [flutterViewController updateViewportMetrics]; + + KeyboardAnimationView* keyboardView = + (KeyboardAnimationView*)[flutterViewController keyboardAnimationView]; + // if (keyboardView.startTime == fml::TimePoint()) { + // NSLog(@"first TP"); + // keyboardView.startTime = recorder.get() -> GetVsyncTargetTime(); + // } + fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; + + double keyboardAnimationTimeMillis = keyboardView.duration * 2 * 1000; + double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; + double expectedFrames = frameRate * keyboardView.duration * 2; + + NSLog(@"timeElapsed %f, duration: %f, rate: %f", timeElapsed.ToMillisecondsF(), + keyboardView.duration, frameRate); + + double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; + int frameApproximation = roundf(MIN(percentComplete, 1) * expectedFrames); + double animationFramePercentage = + [[kKeyboardStops objectAtIndex:frameApproximation] doubleValue]; + + CGFloat newY = + keyboardView.from + animationFramePercentage * (keyboardView.to - keyboardView.from); + + NSLog(@"frame %d, expected %f, complete: %f", frameApproximation, expectedFrames, + animationFramePercentage); + + CGFloat value = + [flutterViewController keyboardAnimationView].layer.presentationLayer.frame.origin.y; + + NSLog(@"oldY %f, newY: %f", value, newY); + + if (animationFramePercentage == keyboardView.lastComplete) { + [flutterViewController invalidateKeyboardAnimationVSyncClient]; + [flutterViewController removeKeyboardAnimationView]; + [flutterViewController ensureViewportMetricsIsCorrect]; + return; } + + keyboardView.lastComplete = animationFramePercentage; + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; + [flutterViewController updateViewportMetrics]; }; flutter::Shell& shell = [_engine.get() shell]; NSAssert(_keyboardAnimationVSyncClient == nil, @@ -1430,6 +1686,7 @@ - (void)ensureViewportMetricsIsCorrect { // Make sure the `physical_view_inset_bottom` is the target value. _viewportMetrics.physical_view_inset_bottom = self.targetViewInsetBottom; [self updateViewportMetrics]; + NSLog(@"updating ensure"); } } From c6dab59d78c13e3aebf1394a78a642ea327ccb49 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 14 Nov 2022 15:22:06 -0600 Subject: [PATCH 02/53] first keyboard commit --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 66f1ae80fbb71..d6e6fcc6fc9c1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1506,7 +1506,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { //// double smoothedInset = roundf(*inset); //// //// if (abs(flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom - - ///smoothedInset) < 1) { / NSLog(@"<1"); / return; / } + /// smoothedInset) < 1) { / NSLog(@"<1"); / return; / } // NSLog(@"drawing %f", *inset); // // flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = *inset; From 6e64b0dc6657b2da6c6db60afbddd7c417befa05 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 14 Nov 2022 16:54:54 -0600 Subject: [PATCH 03/53] cleaned up from initial testing --- .../framework/Source/FlutterViewController.mm | 217 +++--------------- 1 file changed, 30 insertions(+), 187 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 27027a8eadffb..c9dc1060c85d4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -33,6 +33,9 @@ static constexpr int kMicrosecondsPerSecond = 1000 * 1000; static constexpr CGFloat kScrollViewContentSize = 2.0; + +// Calculated based on spring interpolation from https://github.com/CosynPa/RevealSpringAnimation +// (supports max 120fps) static NSArray* const kKeyboardStops = @[ @0.0104644605808437, @0.0378945710726348, @@ -163,41 +166,6 @@ - (void)addInternalPlugins; - (void)deregisterNotifications; @end -@interface KeyboardAnimationLayer : CALayer -@property(nonatomic) CGFloat keyboardAnimationViewInsetBottom; -@property(nonatomic, copy) void (^updateViewport)(CGFloat* inset); -@end - -@implementation KeyboardAnimationLayer -@dynamic keyboardAnimationViewInsetBottom; - -- (id)initWithLayer:(id)layer { - if ((self = [super initWithLayer:layer])) { - // Check if it's the right class before casting - if ([layer isKindOfClass:[KeyboardAnimationLayer class]]) { - // Copy the value of "myProperty" over from the other layer - self.updateViewport = ((KeyboardAnimationLayer*)layer).updateViewport; - } - } - return self; -} - -+ (BOOL)needsDisplayForKey:(NSString*)key { - if ([key isEqualToString:@"keyboardAnimationViewInsetBottom"]) - return YES; - - return [super needsDisplayForKey:key]; -} - -- (void)drawInContext:(CGContextRef)ctx { - CGFloat newViewInsetBottom = self.keyboardAnimationViewInsetBottom; - - if (self.updateViewport) { - self.updateViewport(&newViewInsetBottom); - } -} -@end - @interface KeyboardAnimationView : UIView @property(nonatomic) fml::TimePoint startTime; @property(nonatomic) NSTimeInterval duration; @@ -206,14 +174,7 @@ @interface KeyboardAnimationView : UIView @property(nonatomic) double lastComplete; @end -@implementation KeyboardAnimationView -+ (Class)layerClass { - return [KeyboardAnimationLayer class]; -} - -- (void)drawRect:(CGRect)rect { -} - +@implementation KeyboardAnimationView : UIView @end @implementation FlutterViewController { @@ -1312,8 +1273,6 @@ - (void)invalidateTouchRateCorrectionVSyncClient { #pragma mark - Handle view resizing - (void)updateViewportMetrics { - NSLog(@"updating viewport"); - if ([_engine.get() viewController] == self) { [_engine.get() updateViewportMetrics:_viewportMetrics]; } @@ -1329,11 +1288,6 @@ - (CGFloat)statusBarPadding { } - (void)viewDidLayoutSubviews { - // if ([self keyboardAnimationView].superview != nil) { - // NSLog(@"escaped subview"); - // return; - // } - CGRect viewBounds = self.view.bounds; CGFloat scale = [UIScreen mainScreen].scale; @@ -1350,8 +1304,6 @@ - (void)viewDidLayoutSubviews { [self updateViewportPadding]; [self updateViewportMetrics]; - NSLog(@"updating subviews %@", NSStringFromCGRect(viewBounds)); - // There is no guarantee that UIKit will layout subviews when the application is active. Creating // the surface when inactive will cause GPU accesses from the background. Only wait for the first // frame to render when the application is actually active. @@ -1381,7 +1333,6 @@ - (void)viewDidLayoutSubviews { - (void)viewSafeAreaInsetsDidChange { [self updateViewportPadding]; [self updateViewportMetrics]; - NSLog(@"updating safe area"); [super viewSafeAreaInsetsDidChange]; } @@ -1487,117 +1438,31 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; [self setupKeyboardAnimationVsyncClient]; - // VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient; - - // ((KeyboardAnimationLayer *) [self keyboardAnimationView].layer).updateViewport = ^(CGFloat - // *inset) { - // fml::WeakPtr weakSelf = [self getWeakPtr]; - // if (!weakSelf) { - // return; - // } - // - // fml::scoped_nsobject - // flutterViewController([(FlutterViewController*)weakSelf.get() retain]); - // - // if (!flutterViewController) { - // return; - // } - // - //// double smoothedInset = roundf(*inset); - //// - //// if (abs(flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom - - /// smoothedInset) < 1) { / NSLog(@"<1"); / return; / } - // NSLog(@"drawing %f", *inset); - // - // flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = *inset; - // [flutterViewController updateViewportMetrics]; - // }; - - // double from = [self keyboardAnimationView].layer.presentationLayer.frame.origin.y; - // - // ((KeyboardAnimationLayer *) [self - // keyboardAnimationView].layer).keyboardAnimationViewInsetBottom = self.targetViewInsetBottom; - // [CATransaction begin]; - // CASpringAnimation *keyboardViewAnimation = [CASpringAnimation - // animationWithKeyPath:@"keyboardAnimationViewInsetBottom"]; keyboardViewAnimation.damping = - // 500; keyboardViewAnimation.stiffness = 1000; keyboardViewAnimation.mass = 3; - // keyboardViewAnimation.initialVelocity = 0; - // keyboardViewAnimation.duration = duration * 4; - //// keyboardViewAnimation.fromValue = [NSNumber numberWithFloat:from]; - // keyboardViewAnimation.fromValue = [NSNumber - // numberWithFloat:_viewportMetrics.physical_view_inset_bottom]; keyboardViewAnimation.toValue - // = [NSNumber numberWithFloat:self.targetViewInsetBottom]; if (@available(iOS 15, *)) { - // keyboardViewAnimation.preferredFrameRateRange = CAFrameRateRangeMake(120.0, 120.0, - // 120.0); - // } - // - // NSLog(@"from %f", _viewportMetrics.physical_view_inset_bottom); - // NSLog(@"to %f", self.targetViewInsetBottom); - // [CATransaction setCompletionBlock:^{ - //// if (_keyboardAnimationVSyncClient == currentVsyncClient) { - // // Indicates the vsync client captured by this block is the original one, which also - // // indicates the animation has not been interrupted from its beginning. Moreover, - // // indicates the animation is over and there is no more to execute. - //// [self invalidateKeyboardAnimationVSyncClient]; - //// [self removeKeyboardAnimationView]; - //// [self ensureViewportMetricsIsCorrect]; - //// } - // }]; - // - // [((KeyboardAnimationLayer *) [self keyboardAnimationView].layer) - // addAnimation:keyboardViewAnimation forKey:nil]; [CATransaction commit]; + VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient; [UIView animateWithDuration:duration - delay:0 - options:UIViewAnimationOptionPreferredFramesPerSecond60 - animations:^{ - // Set DisplayLink tracking values - KeyboardAnimationView* keyboardView = - (KeyboardAnimationView*)[self keyboardAnimationView]; - double toY = self.targetViewInsetBottom; - double fromY = _viewportMetrics.physical_view_inset_bottom + - ((toY - _viewportMetrics.physical_view_inset_bottom) * .046); - - // _viewportMetrics.physical_view_inset_bottom = fromY; - // [self updateViewportMetrics]; - - // fromY += ((toY - fromY) * 0.0268); - fromY = _viewportMetrics.physical_view_inset_bottom; - - // NSTimeInterval delayInSeconds = 2; - // dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, - // (int64_t)(delayInSeconds * NSEC_PER_MSEC)); dispatch_after(popTime, - // dispatch_get_main_queue(), ^(void){ - // NSLog(@"delayed"); - // _viewportMetrics.physical_view_inset_bottom = fromY; - // [self updateViewportMetrics]; - // }); - - // keyboardView.startTime = fml::TimePoint();//fml::TimePoint().Now(); - keyboardView.startTime = fml::TimePoint().Now(); - keyboardView.duration = duration; - keyboardView.from = fromY; - keyboardView.to = toY; - - // Set end value. - [self keyboardAnimationView].frame = - CGRectMake(0, self.targetViewInsetBottom, 0, 0); - } - completion:^(BOOL finished){ - // if (_keyboardAnimationVSyncClient == currentVsyncClient) { - // // Indicates the vsync client captured by this block is the original - // one, which also - // // indicates the animation has not been interrupted from its beginning. - // Moreover, - // // indicates the animation is over and there is no more to execute. - // KeyboardAnimationView* keyboardView = (KeyboardAnimationView *) [self - // keyboardAnimationView]; - // keyboardView.startTime = fml::TimePoint(); - // [self invalidateKeyboardAnimationVSyncClient]; - // [self removeKeyboardAnimationView]; - // [self ensureViewportMetricsIsCorrect]; - // } - }]; + animations:^{ + // Set DisplayLink tracking values + KeyboardAnimationView* keyboardView = (KeyboardAnimationView*)[self keyboardAnimationView]; + + keyboardView.startTime = fml::TimePoint().Now(); + keyboardView.duration = duration; + keyboardView.from = _viewportMetrics.physical_view_inset_bottom; + keyboardView.to = self.targetViewInsetBottom; + + // Set end value. + [self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); + } + completion:^(BOOL finished) { + if (_keyboardAnimationVSyncClient == currentVsyncClient) { + // Indicates the vsync client captured by this block is the original one, which also + // indicates the animation has not been interrupted from its beginning. Moreover, + // indicates the animation is over and there is no more to execute. + [self invalidateKeyboardAnimationVSyncClient]; + [self removeKeyboardAnimationView]; + [self ensureViewportMetricsIsCorrect]; + } + }]; } - (void)setupKeyboardAnimationVsyncClient { @@ -1619,19 +1484,14 @@ - (void)setupKeyboardAnimationVsyncClient { KeyboardAnimationView* keyboardView = (KeyboardAnimationView*)[flutterViewController keyboardAnimationView]; - // if (keyboardView.startTime == fml::TimePoint()) { - // NSLog(@"first TP"); - // keyboardView.startTime = recorder.get() -> GetVsyncTargetTime(); - // } + fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; + double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; + // Double keyboard animation duration for manual animation synchronization. double keyboardAnimationTimeMillis = keyboardView.duration * 2 * 1000; - double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; double expectedFrames = frameRate * keyboardView.duration * 2; - NSLog(@"timeElapsed %f, duration: %f, rate: %f", timeElapsed.ToMillisecondsF(), - keyboardView.duration, frameRate); - double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; int frameApproximation = roundf(MIN(percentComplete, 1) * expectedFrames); double animationFramePercentage = @@ -1640,22 +1500,6 @@ - (void)setupKeyboardAnimationVsyncClient { CGFloat newY = keyboardView.from + animationFramePercentage * (keyboardView.to - keyboardView.from); - NSLog(@"frame %d, expected %f, complete: %f", frameApproximation, expectedFrames, - animationFramePercentage); - - CGFloat value = - [flutterViewController keyboardAnimationView].layer.presentationLayer.frame.origin.y; - - NSLog(@"oldY %f, newY: %f", value, newY); - - if (animationFramePercentage == keyboardView.lastComplete) { - [flutterViewController invalidateKeyboardAnimationVSyncClient]; - [flutterViewController removeKeyboardAnimationView]; - [flutterViewController ensureViewportMetricsIsCorrect]; - return; - } - - keyboardView.lastComplete = animationFramePercentage; flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; [flutterViewController updateViewportMetrics]; }; @@ -1686,7 +1530,6 @@ - (void)ensureViewportMetricsIsCorrect { // Make sure the `physical_view_inset_bottom` is the target value. _viewportMetrics.physical_view_inset_bottom = self.targetViewInsetBottom; [self updateViewportMetrics]; - NSLog(@"updating ensure"); } } From 142a133d01ebc58840801d935ee131175438f250 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 23 Nov 2022 21:40:02 -0600 Subject: [PATCH 04/53] added keyboard animation stop calculation --- .../framework/Source/FlutterViewController.mm | 170 ++++++++++-------- 1 file changed, 97 insertions(+), 73 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index c9dc1060c85d4..ca4fc8222614f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -34,72 +34,6 @@ static constexpr int kMicrosecondsPerSecond = 1000 * 1000; static constexpr CGFloat kScrollViewContentSize = 2.0; -// Calculated based on spring interpolation from https://github.com/CosynPa/RevealSpringAnimation -// (supports max 120fps) -static NSArray* const kKeyboardStops = @[ - @0.0104644605808437, - @0.0378945710726348, - @0.0772929434500889, - @0.124734869398615, - @0.177165077399258, - @0.232230492534301, - @0.288142884918677, - @0.343566301349425, - @0.397525020517165, - @0.449328480839688, - @0.498510223492776, - @0.544778389928067, - @0.587975728583874, - @0.628047412666313, - @0.665015260793732, - @0.698957194203488, - @0.729990965893388, - @0.758261365048704, - @0.783930239883246, - @0.807168798214102, - @0.828151741567624, - @0.847052868629256, - @0.864041850126972, - @0.879281932061502, - @0.89292836947895, - @0.905127430324033, - @0.91601583965413, - @0.925720559754988, - @0.934358822413164, - @0.942038346548186, - @0.94885768823959, - @0.954906681442232, - @0.960266936818686, - @0.965012373505042, - @0.969209764577412, - @0.972919281759302, - @0.97619502871874, - @0.979085555325651, - @0.981634347620896, - @0.983880290109166, - @0.985858098428022, - @0.987598721546455, - @0.989129713475411, - @0.99047557508464, - @0.991658067059749, - @0.992696495336988, - @0.993607970550618, - @0.994407643142672, - @0.995108915836915, - @0.995723635183195, - @0.996262263847681, - @0.996734035268435, - @0.997147092222164, - @0.997508610762945, - @0.997824910901819, - @0.99810155530117, - @0.998343437162298, - @0.998554858390709, - @0.998739599032797, - @0.998900978890739, - @1 -]; - static NSString* const kFlutterRestorationStateAppData = @"FlutterRestorationStateAppData"; NSNotificationName const FlutterSemanticsUpdateNotification = @"FlutterSemanticsUpdate"; @@ -177,6 +111,59 @@ @interface KeyboardAnimationView : UIView @implementation KeyboardAnimationView : UIView @end +@interface KeyboardSpringCurve : NSObject +@property(nonatomic) double initialVelocity; +@property(nonatomic) double settlingDuration; +@property(nonatomic) double response; +@property(nonatomic) double dampingRatio; +@property(nonatomic) double omega; +@end + +@implementation KeyboardSpringCurve +- (id)initWithStiffness:(double)stiffness + dampingRatio:(double)dampingRatio + mass:(double)mass + initialVelocity:(double)initialVelocity + settlingDuration:(double)settlingDuration { + self = [super init]; + if (self) { + _dampingRatio = dampingRatio; + _initialVelocity = initialVelocity; + _settlingDuration = settlingDuration; + _response = MAX(1e-5, 2 * M_PI / sqrt(stiffness / mass)); + _omega = 2 * M_PI / _response; + } + return self; +} + +- (double)curveFunc:(double)t { + double v0 = self.initialVelocity; + double zeta = self.dampingRatio; + + double y; + if (abs(zeta - 1.0) < 1e-8) { + double c1 = -1.0; + double c2 = v0 - self.omega; + y = (c1 + c2 * t) * exp(-self.omega * t); + } else if (zeta > 1) { + double s1 = self.omega * (-zeta + sqrt(zeta * zeta - 1)); + double s2 = self.omega * (-zeta - sqrt(zeta * zeta - 1)); + double c1 = (-s2 - v0) / (s2 - s1); + double c2 = (s1 + v0) / (s2 - s1); + y = c1 * exp(s1 * t) + c2 * exp(s2 * t); + } else { + double a = -self.omega * zeta; + double b = self.omega * sqrt(1 - zeta * zeta); + double c2 = (v0 + a) / b; + double theta = atan(c2); + // Alternatively y = (-cos(b * t) + c2 * sin(b * t)) * exp(a * t) + y = sqrt(1 + c2 * c2) * exp(a * t) * cos(b * t + theta + M_PI); + } + + return y + 1; +} +@end + @implementation FlutterViewController { std::unique_ptr> _weakFactory; fml::scoped_nsobject _engine; @@ -199,6 +186,8 @@ @implementation FlutterViewController { // https://github.com/flutter/flutter/issues/35050 fml::scoped_nsobject _scrollView; fml::scoped_nsobject _keyboardAnimationView; + fml::scoped_nsobject _keyboardSpringCurve; + fml::scoped_nsobject _keyboardAnimationStops; MouseState _mouseState; // Timestamp after which a scroll inertia cancel event should be inferred. NSTimeInterval _scrollInertiaEventStartline; @@ -340,6 +329,7 @@ - (void)performCommonViewControllerInitialization { _statusBarStyle = UIStatusBarStyleDefault; [self setupNotificationCenterObservers]; + [self setupKeyboardAnimationCurve]; } - (FlutterEngine*)engine { @@ -455,6 +445,31 @@ - (void)setupNotificationCenterObservers { object:nil]; } +- (void)setupKeyboardAnimationCurve { + _keyboardAnimationStops.reset([[NSMutableArray alloc] init]); + + // Keyboard animation spring curve for iOS 16 and below + _keyboardSpringCurve.reset([[KeyboardSpringCurve alloc] initWithStiffness:1000 + dampingRatio:1 + mass:3 + initialVelocity:0 + settlingDuration:0.5]); + double settlingDuration = [[self keyboardSpringCurve] settlingDuration]; + double displayRefreshRate = [DisplayLinkManager displayRefreshRate]; + + for (double time = 1 / displayRefreshRate; time < settlingDuration * 2; + time += 1 / displayRefreshRate) { + NSNumber* keyboardAnimationStop = + [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:time]]; + if (time <= settlingDuration + 1 / displayRefreshRate) { + [[self keyboardAnimationStops] addObject:keyboardAnimationStop]; + } else { + [[self keyboardAnimationStops] addObject:[NSNumber numberWithDouble:1]]; + break; + } + } +} + - (void)setInitialRoute:(NSString*)route { [[_engine.get() navigationChannel] invokeMethod:@"setInitialRoute" arguments:route]; } @@ -665,6 +680,14 @@ - (UIView*)keyboardAnimationView { return _keyboardAnimationView.get(); } +- (KeyboardSpringCurve*)keyboardSpringCurve { + return _keyboardSpringCurve.get(); +} + +- (NSMutableArray*)keyboardAnimationStops { + return _keyboardAnimationStops.get(); +} + - (BOOL)loadDefaultSplashScreenView { NSString* launchscreenName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"]; @@ -1487,15 +1510,16 @@ - (void)setupKeyboardAnimationVsyncClient { fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; - - // Double keyboard animation duration for manual animation synchronization. - double keyboardAnimationTimeMillis = keyboardView.duration * 2 * 1000; - double expectedFrames = frameRate * keyboardView.duration * 2; - + + double animationSettlingDuration = [flutterViewController keyboardSpringCurve].settlingDuration; + double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; + + double expectedFrames = frameRate * animationSettlingDuration; double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; + int frameApproximation = roundf(MIN(percentComplete, 1) * expectedFrames); - double animationFramePercentage = - [[kKeyboardStops objectAtIndex:frameApproximation] doubleValue]; + double animationFramePercentage = [[[flutterViewController keyboardAnimationStops] + objectAtIndex:frameApproximation] doubleValue]; CGFloat newY = keyboardView.from + animationFramePercentage * (keyboardView.to - keyboardView.from); From 96c4c5de53cf1f9470109b2d2b1e0db6bb38c35a Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 23 Nov 2022 21:40:58 -0600 Subject: [PATCH 05/53] added keyboard animation stop calculation --- .../darwin/ios/framework/Source/FlutterViewController.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ca4fc8222614f..352ff71f44f3c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1510,13 +1510,13 @@ - (void)setupKeyboardAnimationVsyncClient { fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; - + double animationSettlingDuration = [flutterViewController keyboardSpringCurve].settlingDuration; double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; - + double expectedFrames = frameRate * animationSettlingDuration; double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; - + int frameApproximation = roundf(MIN(percentComplete, 1) * expectedFrames); double animationFramePercentage = [[[flutterViewController keyboardAnimationStops] objectAtIndex:frameApproximation] doubleValue]; From cc31006af561b002c4faed59fc7edadbc302bfc0 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 24 Nov 2022 01:47:00 -0600 Subject: [PATCH 06/53] lucky updates :) --- .../framework/Source/FlutterViewController.mm | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 352ff71f44f3c..a273dacbc6aa1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -105,7 +105,6 @@ @interface KeyboardAnimationView : UIView @property(nonatomic) NSTimeInterval duration; @property(nonatomic) CGFloat from; @property(nonatomic) CGFloat to; -@property(nonatomic) double lastComplete; @end @implementation KeyboardAnimationView : UIView @@ -114,7 +113,6 @@ @implementation KeyboardAnimationView : UIView @interface KeyboardSpringCurve : NSObject @property(nonatomic) double initialVelocity; @property(nonatomic) double settlingDuration; -@property(nonatomic) double response; @property(nonatomic) double dampingRatio; @property(nonatomic) double omega; @end @@ -130,8 +128,9 @@ - (id)initWithStiffness:(double)stiffness _dampingRatio = dampingRatio; _initialVelocity = initialVelocity; _settlingDuration = settlingDuration; - _response = MAX(1e-5, 2 * M_PI / sqrt(stiffness / mass)); - _omega = 2 * M_PI / _response; + + double response = MAX(1e-5, 2 * M_PI / sqrt(stiffness / mass)); + _omega = 2 * M_PI / response; } return self; } @@ -455,18 +454,23 @@ - (void)setupKeyboardAnimationCurve { initialVelocity:0 settlingDuration:0.5]); double settlingDuration = [[self keyboardSpringCurve] settlingDuration]; - double displayRefreshRate = [DisplayLinkManager displayRefreshRate]; + double maxFramesPerSecond = [DisplayLinkManager displayRefreshRate]; + double frameDuration = 1 / maxFramesPerSecond; - for (double time = 1 / displayRefreshRate; time < settlingDuration * 2; - time += 1 / displayRefreshRate) { + // Calculating a full seconds worth of animation stops for increased stop fidelity + for (double time = 1; time < maxFramesPerSecond; time++) { + double frameTime = time / maxFramesPerSecond; NSNumber* keyboardAnimationStop = - [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:time]]; - if (time <= settlingDuration + 1 / displayRefreshRate) { - [[self keyboardAnimationStops] addObject:keyboardAnimationStop]; - } else { + [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:frameTime]]; + + // Only store animation stops up to the settlingDuration plus one (since time starts at 1) + if (frameTime > settlingDuration + frameDuration) { + // Ensure the animation finishes with 100% [[self keyboardAnimationStops] addObject:[NSNumber numberWithDouble:1]]; break; } + + [[self keyboardAnimationStops] addObject:keyboardAnimationStop]; } } @@ -1456,7 +1460,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Set animation begin value. [self keyboardAnimationView].frame = - CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 1, 1); + CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0); // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; @@ -1509,15 +1513,19 @@ - (void)setupKeyboardAnimationVsyncClient { (KeyboardAnimationView*)[flutterViewController keyboardAnimationView]; fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; - double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; + double maxFrameRate = [DisplayLinkManager displayRefreshRate]; + double frameRate = round( + 1 / + (recorder.get()->GetVsyncTargetTime() - recorder.get()->GetVsyncStartTime()).ToSecondsF()); double animationSettlingDuration = [flutterViewController keyboardSpringCurve].settlingDuration; double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; - double expectedFrames = frameRate * animationSettlingDuration; + double expectedFrames = frameRate * animationSettlingDuration * (maxFrameRate / frameRate); double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; - int frameApproximation = roundf(MIN(percentComplete, 1) * expectedFrames); + int frameApproximation = MIN(roundf(MIN(percentComplete, 1) * expectedFrames), + [[flutterViewController keyboardAnimationStops] count]); double animationFramePercentage = [[[flutterViewController keyboardAnimationStops] objectAtIndex:frameApproximation] doubleValue]; From ad15be499ddfd96c63cb4f99a0d668f382c47db9 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 24 Nov 2022 02:07:25 -0600 Subject: [PATCH 07/53] type change --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index a273dacbc6aa1..bc7a41dab3b85 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -458,7 +458,7 @@ - (void)setupKeyboardAnimationCurve { double frameDuration = 1 / maxFramesPerSecond; // Calculating a full seconds worth of animation stops for increased stop fidelity - for (double time = 1; time < maxFramesPerSecond; time++) { + for (int time = 1; time < maxFramesPerSecond; time++) { double frameTime = time / maxFramesPerSecond; NSNumber* keyboardAnimationStop = [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:frameTime]]; From a5027d3b81a02a75adaf4561603010e605a92b20 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Sun, 27 Nov 2022 16:57:41 -0600 Subject: [PATCH 08/53] final touches --- .../framework/Source/FlutterViewController.mm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index bc7a41dab3b85..2d872bb3d7c2d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -457,11 +457,9 @@ - (void)setupKeyboardAnimationCurve { double maxFramesPerSecond = [DisplayLinkManager displayRefreshRate]; double frameDuration = 1 / maxFramesPerSecond; - // Calculating a full seconds worth of animation stops for increased stop fidelity - for (int time = 1; time < maxFramesPerSecond; time++) { + // Calculating animation stops for twice the settlingDuration for increased fidelity + for (int time = 1; time < roundf(maxFramesPerSecond * settlingDuration * 2); time++) { double frameTime = time / maxFramesPerSecond; - NSNumber* keyboardAnimationStop = - [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:frameTime]]; // Only store animation stops up to the settlingDuration plus one (since time starts at 1) if (frameTime > settlingDuration + frameDuration) { @@ -469,7 +467,9 @@ - (void)setupKeyboardAnimationCurve { [[self keyboardAnimationStops] addObject:[NSNumber numberWithDouble:1]]; break; } - + + NSNumber* keyboardAnimationStop = + [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:frameTime]]; [[self keyboardAnimationStops] addObject:keyboardAnimationStop]; } } @@ -1514,9 +1514,9 @@ - (void)setupKeyboardAnimationVsyncClient { fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; double maxFrameRate = [DisplayLinkManager displayRefreshRate]; - double frameRate = round( - 1 / - (recorder.get()->GetVsyncTargetTime() - recorder.get()->GetVsyncStartTime()).ToSecondsF()); + double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; + + NSLog(@"%f", frameRate); double animationSettlingDuration = [flutterViewController keyboardSpringCurve].settlingDuration; double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; @@ -1525,7 +1525,7 @@ - (void)setupKeyboardAnimationVsyncClient { double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; int frameApproximation = MIN(roundf(MIN(percentComplete, 1) * expectedFrames), - [[flutterViewController keyboardAnimationStops] count]); + [flutterViewController keyboardAnimationStops].count - 1); double animationFramePercentage = [[[flutterViewController keyboardAnimationStops] objectAtIndex:frameApproximation] doubleValue]; From 84d7b8e5c92c10660087381887f7cfb5a8183423 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Sun, 27 Nov 2022 16:58:55 -0600 Subject: [PATCH 09/53] final touches --- .../darwin/ios/framework/Source/FlutterViewController.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 2d872bb3d7c2d..8851173fd7f89 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -467,9 +467,9 @@ - (void)setupKeyboardAnimationCurve { [[self keyboardAnimationStops] addObject:[NSNumber numberWithDouble:1]]; break; } - + NSNumber* keyboardAnimationStop = - [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:frameTime]]; + [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:frameTime]]; [[self keyboardAnimationStops] addObject:keyboardAnimationStop]; } } @@ -1515,7 +1515,7 @@ - (void)setupKeyboardAnimationVsyncClient { fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; double maxFrameRate = [DisplayLinkManager displayRefreshRate]; double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; - + NSLog(@"%f", frameRate); double animationSettlingDuration = [flutterViewController keyboardSpringCurve].settlingDuration; From 1e208d1c13511e578d41e24f5a27bbf68f5f9fdc Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Sun, 27 Nov 2022 16:59:23 -0600 Subject: [PATCH 10/53] final touches --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 8851173fd7f89..65ac9f0da30b3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1516,8 +1516,6 @@ - (void)setupKeyboardAnimationVsyncClient { double maxFrameRate = [DisplayLinkManager displayRefreshRate]; double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; - NSLog(@"%f", frameRate); - double animationSettlingDuration = [flutterViewController keyboardSpringCurve].settlingDuration; double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; From 2b3743e48b9a9bc1ddd97d4dadc2c5f2a8845525 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 30 Nov 2022 23:16:14 -0600 Subject: [PATCH 11/53] removed KeyboardAnimationView class --- .../framework/Source/FlutterViewController.mm | 41 ++++++++----------- .../framework/Source/keyboard_spring_ios.m | 8 ++++ 2 files changed, 24 insertions(+), 25 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 65ac9f0da30b3..478c80be8ca1e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -65,6 +65,10 @@ @interface FlutterViewController () _scrollView; - fml::scoped_nsobject _keyboardAnimationView; + fml::scoped_nsobject _keyboardAnimationView; fml::scoped_nsobject _keyboardSpringCurve; fml::scoped_nsobject _keyboardAnimationStops; MouseState _mouseState; @@ -453,7 +447,7 @@ - (void)setupKeyboardAnimationCurve { mass:3 initialVelocity:0 settlingDuration:0.5]); - double settlingDuration = [[self keyboardSpringCurve] settlingDuration]; + double settlingDuration = [self keyboardSpringCurve].settlingDuration; double maxFramesPerSecond = [DisplayLinkManager displayRefreshRate]; double frameDuration = 1 / maxFramesPerSecond; @@ -1446,7 +1440,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // When call this method first time, // initialize the keyboardAnimationView to get animation interpolation during animation. if ([self keyboardAnimationView] == nil) { - KeyboardAnimationView* keyboardAnimationView = [[KeyboardAnimationView alloc] init]; + UIView* keyboardAnimationView = [[UIView alloc] init]; [keyboardAnimationView setHidden:YES]; _keyboardAnimationView.reset(keyboardAnimationView); } @@ -1470,12 +1464,10 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { [UIView animateWithDuration:duration animations:^{ // Set DisplayLink tracking values - KeyboardAnimationView* keyboardView = (KeyboardAnimationView*)[self keyboardAnimationView]; - - keyboardView.startTime = fml::TimePoint().Now(); - keyboardView.duration = duration; - keyboardView.from = _viewportMetrics.physical_view_inset_bottom; - keyboardView.to = self.targetViewInsetBottom; + self.keyboardAnimationStartTime = fml::TimePoint().Now(); + self.keyboardAnimationDuration = duration; + self.keyboardAnimationFrom = _viewportMetrics.physical_view_inset_bottom; + self.keyboardAnimationTo = self.targetViewInsetBottom; // Set end value. [self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); @@ -1509,10 +1501,8 @@ - (void)setupKeyboardAnimationVsyncClient { [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; } - KeyboardAnimationView* keyboardView = - (KeyboardAnimationView*)[flutterViewController keyboardAnimationView]; - - fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - keyboardView.startTime; + fml::TimeDelta timeElapsed = + recorder.get()->GetVsyncTargetTime() - [flutterViewController keyboardAnimationStartTime]; double maxFrameRate = [DisplayLinkManager displayRefreshRate]; double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; @@ -1527,8 +1517,9 @@ - (void)setupKeyboardAnimationVsyncClient { double animationFramePercentage = [[[flutterViewController keyboardAnimationStops] objectAtIndex:frameApproximation] doubleValue]; - CGFloat newY = - keyboardView.from + animationFramePercentage * (keyboardView.to - keyboardView.from); + CGFloat newY = [flutterViewController keyboardAnimationFrom] + + animationFramePercentage * ([flutterViewController keyboardAnimationTo] - + [flutterViewController keyboardAnimationFrom]); flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; [flutterViewController updateViewportMetrics]; diff --git a/shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m b/shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m new file mode 100644 index 0000000000000..8809f10e45dde --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m @@ -0,0 +1,8 @@ +// +// keyboard_spring_ios.m +// sources +// +// Created by Jake Schafer on 11/30/22. +// + +#import From b38cad086a49f6a55e1c7770f599b0d5ccf47edd Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 30 Nov 2022 23:17:39 -0600 Subject: [PATCH 12/53] removed KeyboardAnimationView class --- .../darwin/ios/framework/Source/keyboard_spring_ios.m | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m diff --git a/shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m b/shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m deleted file mode 100644 index 8809f10e45dde..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/keyboard_spring_ios.m +++ /dev/null @@ -1,8 +0,0 @@ -// -// keyboard_spring_ios.m -// sources -// -// Created by Jake Schafer on 11/30/22. -// - -#import From 0aeccc9d6e992aa8e812f9faec7c70cad3d9ae57 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 1 Dec 2022 18:18:28 -0600 Subject: [PATCH 13/53] dynamic keyboard spring curve implementation --- .../framework/Source/FlutterViewController.mm | 112 +++++++++--------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 478c80be8ca1e..adc145ca9acb0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -108,18 +108,19 @@ @interface KeyboardSpringCurve : NSObject @property(nonatomic) double initialVelocity; @property(nonatomic) double settlingDuration; @property(nonatomic) double dampingRatio; +@property(nonatomic) double damping; @property(nonatomic) double omega; @end @implementation KeyboardSpringCurve - (id)initWithStiffness:(double)stiffness - dampingRatio:(double)dampingRatio + damping:(double)damping mass:(double)mass initialVelocity:(double)initialVelocity settlingDuration:(double)settlingDuration { self = [super init]; if (self) { - _dampingRatio = dampingRatio; + _dampingRatio = 1; _initialVelocity = initialVelocity; _settlingDuration = settlingDuration; @@ -180,7 +181,6 @@ @implementation FlutterViewController { fml::scoped_nsobject _scrollView; fml::scoped_nsobject _keyboardAnimationView; fml::scoped_nsobject _keyboardSpringCurve; - fml::scoped_nsobject _keyboardAnimationStops; MouseState _mouseState; // Timestamp after which a scroll inertia cancel event should be inferred. NSTimeInterval _scrollInertiaEventStartline; @@ -322,7 +322,6 @@ - (void)performCommonViewControllerInitialization { _statusBarStyle = UIStatusBarStyleDefault; [self setupNotificationCenterObservers]; - [self setupKeyboardAnimationCurve]; } - (FlutterEngine*)engine { @@ -438,36 +437,6 @@ - (void)setupNotificationCenterObservers { object:nil]; } -- (void)setupKeyboardAnimationCurve { - _keyboardAnimationStops.reset([[NSMutableArray alloc] init]); - - // Keyboard animation spring curve for iOS 16 and below - _keyboardSpringCurve.reset([[KeyboardSpringCurve alloc] initWithStiffness:1000 - dampingRatio:1 - mass:3 - initialVelocity:0 - settlingDuration:0.5]); - double settlingDuration = [self keyboardSpringCurve].settlingDuration; - double maxFramesPerSecond = [DisplayLinkManager displayRefreshRate]; - double frameDuration = 1 / maxFramesPerSecond; - - // Calculating animation stops for twice the settlingDuration for increased fidelity - for (int time = 1; time < roundf(maxFramesPerSecond * settlingDuration * 2); time++) { - double frameTime = time / maxFramesPerSecond; - - // Only store animation stops up to the settlingDuration plus one (since time starts at 1) - if (frameTime > settlingDuration + frameDuration) { - // Ensure the animation finishes with 100% - [[self keyboardAnimationStops] addObject:[NSNumber numberWithDouble:1]]; - break; - } - - NSNumber* keyboardAnimationStop = - [NSNumber numberWithDouble:[[self keyboardSpringCurve] curveFunc:frameTime]]; - [[self keyboardAnimationStops] addObject:keyboardAnimationStop]; - } -} - - (void)setInitialRoute:(NSString*)route { [[_engine.get() navigationChannel] invokeMethod:@"setInitialRoute" arguments:route]; } @@ -682,10 +651,6 @@ - (KeyboardSpringCurve*)keyboardSpringCurve { return _keyboardSpringCurve.get(); } -- (NSMutableArray*)keyboardAnimationStops { - return _keyboardAnimationStops.get(); -} - - (BOOL)loadDefaultSplashScreenView { NSString* launchscreenName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"]; @@ -1463,14 +1428,32 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { [UIView animateWithDuration:duration animations:^{ - // Set DisplayLink tracking values - self.keyboardAnimationStartTime = fml::TimePoint().Now(); - self.keyboardAnimationDuration = duration; - self.keyboardAnimationFrom = _viewportMetrics.physical_view_inset_bottom; - self.keyboardAnimationTo = self.targetViewInsetBottom; - // Set end value. [self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); + + // Set keyboard spring animation details (if animation is CASpringAnimation). + CAAnimation* keyboardAnimation = + [[self keyboardAnimationView].layer animationForKey:@"position"]; + if ([keyboardAnimation isKindOfClass:[CASpringAnimation class]]) { + CASpringAnimation* keyboardSpringAnimation = (CASpringAnimation*)keyboardAnimation; + _keyboardSpringCurve.reset([[KeyboardSpringCurve alloc] + initWithStiffness:keyboardSpringAnimation.stiffness + damping:keyboardSpringAnimation.damping + mass:keyboardSpringAnimation.mass + initialVelocity:keyboardSpringAnimation.initialVelocity + settlingDuration:0.5]); + + // Set DisplayLink tracking values + self.keyboardAnimationStartTime = fml::TimePoint().Now(); + self.keyboardAnimationFrom = _viewportMetrics.physical_view_inset_bottom; + self.keyboardAnimationTo = self.targetViewInsetBottom; + + // Double duration to match actual timing of spring animation + self.keyboardAnimationDuration = duration * 2; + } else { + // Reset to use fallback keyboard animation tracking. + _keyboardSpringCurve.reset(); + } } completion:^(BOOL finished) { if (_keyboardAnimationVSyncClient == currentVsyncClient) { @@ -1501,25 +1484,36 @@ - (void)setupKeyboardAnimationVsyncClient { [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; } - fml::TimeDelta timeElapsed = - recorder.get()->GetVsyncTargetTime() - [flutterViewController keyboardAnimationStartTime]; - double maxFrameRate = [DisplayLinkManager displayRefreshRate]; - double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; + CGFloat newY; + if ([flutterViewController keyboardSpringCurve] == nil) { + newY = [flutterViewController keyboardAnimationView].layer.presentationLayer.frame.origin.y; + } else { + fml::TimeDelta timeElapsed = + recorder.get()->GetVsyncTargetTime() - [flutterViewController keyboardAnimationStartTime]; + double maxFrameRate = [DisplayLinkManager displayRefreshRate]; + double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; - double animationSettlingDuration = [flutterViewController keyboardSpringCurve].settlingDuration; - double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; + double animationSettlingDuration = [flutterViewController keyboardAnimationDuration]; + double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; - double expectedFrames = frameRate * animationSettlingDuration * (maxFrameRate / frameRate); - double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; + double expectedFrames = frameRate * animationSettlingDuration * (maxFrameRate / frameRate); + double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; - int frameApproximation = MIN(roundf(MIN(percentComplete, 1) * expectedFrames), - [flutterViewController keyboardAnimationStops].count - 1); - double animationFramePercentage = [[[flutterViewController keyboardAnimationStops] - objectAtIndex:frameApproximation] doubleValue]; + int frameApproximation = roundf(MIN(percentComplete, 1) * expectedFrames); + double frameTime = frameApproximation / maxFrameRate; - CGFloat newY = [flutterViewController keyboardAnimationFrom] + - animationFramePercentage * ([flutterViewController keyboardAnimationTo] - - [flutterViewController keyboardAnimationFrom]); + double keyboardAnimationStop; + // Ensure the animation finishes with 100% + if (frameApproximation == expectedFrames) { + keyboardAnimationStop = 1; + } else { + keyboardAnimationStop = [[flutterViewController keyboardSpringCurve] curveFunc:frameTime]; + } + + newY = [flutterViewController keyboardAnimationFrom] + + keyboardAnimationStop * ([flutterViewController keyboardAnimationTo] - + [flutterViewController keyboardAnimationFrom]); + } flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; [flutterViewController updateViewportMetrics]; From 1cd784a71601a8c4789d071654749c16651277fd Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 1 Dec 2022 19:11:01 -0600 Subject: [PATCH 14/53] broke out spring curve to own objc files --- shell/platform/darwin/ios/BUILD.gn | 2 + .../framework/Source/FlutterViewController.mm | 55 +------------------ .../ios/framework/Source/spring_curve_ios.h | 27 +++++++++ .../ios/framework/Source/spring_curve_ios.mm | 53 ++++++++++++++++++ 4 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/spring_curve_ios.h create mode 100644 shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 52e626188993c..9628f120a3594 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -107,6 +107,8 @@ source_set("flutter_framework_source") { "framework/Source/platform_message_response_darwin.mm", "framework/Source/profiler_metrics_ios.h", "framework/Source/profiler_metrics_ios.mm", + "framework/Source/spring_curve_ios.h", + "framework/Source/spring_curve_ios.mm", "framework/Source/vsync_waiter_ios.h", "framework/Source/vsync_waiter_ios.mm", "ios_context.h", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index adc145ca9acb0..511abad1336f2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -27,6 +27,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h" #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" #import "flutter/shell/platform/embedder/embedder.h" @@ -104,60 +105,6 @@ - (void)addInternalPlugins; - (void)deregisterNotifications; @end -@interface KeyboardSpringCurve : NSObject -@property(nonatomic) double initialVelocity; -@property(nonatomic) double settlingDuration; -@property(nonatomic) double dampingRatio; -@property(nonatomic) double damping; -@property(nonatomic) double omega; -@end - -@implementation KeyboardSpringCurve -- (id)initWithStiffness:(double)stiffness - damping:(double)damping - mass:(double)mass - initialVelocity:(double)initialVelocity - settlingDuration:(double)settlingDuration { - self = [super init]; - if (self) { - _dampingRatio = 1; - _initialVelocity = initialVelocity; - _settlingDuration = settlingDuration; - - double response = MAX(1e-5, 2 * M_PI / sqrt(stiffness / mass)); - _omega = 2 * M_PI / response; - } - return self; -} - -- (double)curveFunc:(double)t { - double v0 = self.initialVelocity; - double zeta = self.dampingRatio; - - double y; - if (abs(zeta - 1.0) < 1e-8) { - double c1 = -1.0; - double c2 = v0 - self.omega; - y = (c1 + c2 * t) * exp(-self.omega * t); - } else if (zeta > 1) { - double s1 = self.omega * (-zeta + sqrt(zeta * zeta - 1)); - double s2 = self.omega * (-zeta - sqrt(zeta * zeta - 1)); - double c1 = (-s2 - v0) / (s2 - s1); - double c2 = (s1 + v0) / (s2 - s1); - y = c1 * exp(s1 * t) + c2 * exp(s2 * t); - } else { - double a = -self.omega * zeta; - double b = self.omega * sqrt(1 - zeta * zeta); - double c2 = (v0 + a) / b; - double theta = atan(c2); - // Alternatively y = (-cos(b * t) + c2 * sin(b * t)) * exp(a * t) - y = sqrt(1 + c2 * c2) * exp(a * t) * cos(b * t + theta + M_PI); - } - - return y + 1; -} -@end - @implementation FlutterViewController { std::unique_ptr> _weakFactory; fml::scoped_nsobject _engine; diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h new file mode 100644 index 0000000000000..cec73d8b51ec8 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ + +#include + +@interface KeyboardSpringCurve : NSObject + +- (instancetype)initWithStiffness:(double)stiffness + damping:(double)damping + mass:(double)mass + initialVelocity:(double)initialVelocity + settlingDuration:(double)settlingDuration; + +- (double)curveFunc:(double)t; + +@property(nonatomic) double initialVelocity; +@property(nonatomic) double settlingDuration; +@property(nonatomic) double dampingRatio; +@property(nonatomic) double damping; +@property(nonatomic) double omega; +@end + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm new file mode 100644 index 0000000000000..be97e2498b75b --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -0,0 +1,53 @@ +// 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/spring_curve_ios.h" + +#include + +@implementation KeyboardSpringCurve +- (instancetype)initWithStiffness:(double)stiffness + damping:(double)damping + mass:(double)mass + initialVelocity:(double)initialVelocity + settlingDuration:(double)settlingDuration { + self = [super init]; + if (self) { + _dampingRatio = 1; + _initialVelocity = initialVelocity; + _settlingDuration = settlingDuration; + + double response = MAX(1e-5, 2 * M_PI / sqrt(stiffness / mass)); + _omega = 2 * M_PI / response; + } + return self; +} + +- (double)curveFunc:(double)t { + double v0 = self.initialVelocity; + double zeta = self.dampingRatio; + + double y; + if (abs(zeta - 1.0) < 1e-8) { + double c1 = -1.0; + double c2 = v0 - self.omega; + y = (c1 + c2 * t) * exp(-self.omega * t); + } else if (zeta > 1) { + double s1 = self.omega * (-zeta + sqrt(zeta * zeta - 1)); + double s2 = self.omega * (-zeta - sqrt(zeta * zeta - 1)); + double c1 = (-s2 - v0) / (s2 - s1); + double c2 = (s1 + v0) / (s2 - s1); + y = c1 * exp(s1 * t) + c2 * exp(s2 * t); + } else { + double a = -self.omega * zeta; + double b = self.omega * sqrt(1 - zeta * zeta); + double c2 = (v0 + a) / b; + double theta = atan(c2); + // Alternatively y = (-cos(b * t) + c2 * sin(b * t)) * exp(a * t) + y = sqrt(1 + c2 * c2) * exp(a * t) * cos(b * t + theta + M_PI); + } + + return y + 1; +} +@end From ef8481c6bdbac5209c2a37274dfcf7a5ad15fb39 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 1 Dec 2022 19:16:00 -0600 Subject: [PATCH 15/53] broke out spring curve to own objc files --- .../darwin/ios/framework/Source/spring_curve_ios.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index be97e2498b75b..08fcb94a1fcd3 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -8,10 +8,10 @@ @implementation KeyboardSpringCurve - (instancetype)initWithStiffness:(double)stiffness - damping:(double)damping - mass:(double)mass - initialVelocity:(double)initialVelocity - settlingDuration:(double)settlingDuration { + damping:(double)damping + mass:(double)mass + initialVelocity:(double)initialVelocity + settlingDuration:(double)settlingDuration { self = [super init]; if (self) { _dampingRatio = 1; From 582f2e0de831db839853318161fb34ffcf42cb24 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 7 Dec 2022 21:50:39 -0600 Subject: [PATCH 16/53] simplified and added test --- ci/licenses_golden/licenses_flutter | 2 + .../framework/Source/FlutterViewController.mm | 90 ++++++++----------- .../Source/FlutterViewControllerTest.mm | 20 +++++ 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index daa6e2755f882..a0bf6a4f97657 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2574,6 +2574,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_messa FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/module.modulemap diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 511abad1336f2..63fe36aec4b68 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -66,10 +66,10 @@ @interface FlutterViewController () recorder) { @@ -1433,33 +1438,16 @@ - (void)setupKeyboardAnimationVsyncClient { CGFloat newY; if ([flutterViewController keyboardSpringCurve] == nil) { - newY = [flutterViewController keyboardAnimationView].layer.presentationLayer.frame.origin.y; + newY = + flutterViewController.get().keyboardAnimationView.layer.presentationLayer.frame.origin.y; } else { - fml::TimeDelta timeElapsed = - recorder.get()->GetVsyncTargetTime() - [flutterViewController keyboardAnimationStartTime]; - double maxFrameRate = [DisplayLinkManager displayRefreshRate]; - double frameRate = [[flutterViewController keyboardAnimationVSyncClient] getRefreshRate]; - - double animationSettlingDuration = [flutterViewController keyboardAnimationDuration]; - double keyboardAnimationTimeMillis = animationSettlingDuration * 1000; - - double expectedFrames = frameRate * animationSettlingDuration * (maxFrameRate / frameRate); - double percentComplete = timeElapsed.ToMillisecondsF() / keyboardAnimationTimeMillis; - - int frameApproximation = roundf(MIN(percentComplete, 1) * expectedFrames); - double frameTime = frameApproximation / maxFrameRate; - - double keyboardAnimationStop; - // Ensure the animation finishes with 100% - if (frameApproximation == expectedFrames) { - keyboardAnimationStop = 1; - } else { - keyboardAnimationStop = [[flutterViewController keyboardSpringCurve] curveFunc:frameTime]; - } - - newY = [flutterViewController keyboardAnimationFrom] + - keyboardAnimationStop * ([flutterViewController keyboardAnimationTo] - - [flutterViewController keyboardAnimationFrom]); + double start = flutterViewController.get().keyboardAnimationFrom; + double end = flutterViewController.get().keyboardAnimationTo; + fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - + flutterViewController.get().keyboardAnimationStartTime; + double keyboardAnimationStop = + [[flutterViewController keyboardSpringCurve] curveFunc:timeElapsed.ToSecondsF()]; + newY = start + (end - start) * keyboardAnimationStop; } flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 7ed4c0592e0d3..82a4ab60eeb20 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -128,6 +128,9 @@ - (void)keyboardWillChangeFrame:(NSNotification*)notification; - (void)keyboardWillBeHidden:(NSNotification*)notification; - (void)startKeyBoardAnimation:(NSTimeInterval)duration; - (void)setupKeyboardAnimationVsyncClient; +- (UIView*)keyboardAnimationView; +- (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation + duration:(double)duration; - (void)ensureViewportMetricsIsCorrect; - (void)invalidateKeyboardAnimationVSyncClient; - (void)addInternalPlugins; @@ -185,6 +188,23 @@ - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationVsyncClient { OCMVerify([viewControllerMock setupKeyboardAnimationVsyncClient]); } +- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationCurveIfNeeded { + 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]; + + CAAnimation* keyboardAnimation = + [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"]; + + OCMVerify([viewControllerMock setupKeyboardAnimationCurveIfNeeded:keyboardAnimation + duration:0.25]); +} + - (void)testkeyboardWillChangeFrameWillStartKeyboardAnimation { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; From 3179417bc2093b48c88fd87a975cc01cad705432 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 7 Dec 2022 22:27:30 -0600 Subject: [PATCH 17/53] modified spring formula to use damping --- shell/platform/darwin/ios/framework/Source/spring_curve_ios.h | 1 - shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index cec73d8b51ec8..3a9320cef6352 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -20,7 +20,6 @@ @property(nonatomic) double initialVelocity; @property(nonatomic) double settlingDuration; @property(nonatomic) double dampingRatio; -@property(nonatomic) double damping; @property(nonatomic) double omega; @end diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index 08fcb94a1fcd3..df1e98e3e9be8 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -14,7 +14,7 @@ - (instancetype)initWithStiffness:(double)stiffness settlingDuration:(double)settlingDuration { self = [super init]; if (self) { - _dampingRatio = 1; + _dampingRatio = MIN(1.0, damping / 2 / sqrt(stiffness * mass)); _initialVelocity = initialVelocity; _settlingDuration = settlingDuration; From 467e1ce097f33dd640c1605dc5570c93612800eb Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 8 Dec 2022 00:30:44 -0600 Subject: [PATCH 18/53] added logic for compounding simultaneous animation calls --- .../framework/Source/FlutterViewController.mm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 63fe36aec4b68..fb39ee05bfeef 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -66,6 +66,8 @@ @interface FlutterViewController () keyboardFrame.origin.y; + + // Flag for simultaneous compounding animation calls. + self.keyboardAnimationIsCompounding = (self.keyboardAnimationIsShowing && keyboardIsShowing) || + (!self.keyboardAnimationIsShowing && !keyboardIsShowing); + + // Mark keyboard as showing or hiding + self.keyboardAnimationIsShowing = keyboardIsShowing; + [self startKeyBoardAnimation:duration]; } @@ -1359,6 +1372,10 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { if ([self keyboardAnimationView].superview == nil) { [self.view addSubview:[self keyboardAnimationView]]; + } else if (self.keyboardAnimationIsCompounding) { + // Update in-flight keyboard animation curve target position. + self.keyboardAnimationTo = self.targetViewInsetBottom; + return; } // Remove running animation when start another animation. From 743673183d04bf17705791c7065368c62c43d331 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 8 Dec 2022 11:50:51 -0600 Subject: [PATCH 19/53] remove unnecessary code --- .../ios/framework/Source/FlutterViewController.mm | 11 +++-------- .../ios/framework/Source/FlutterViewControllerTest.mm | 3 +-- .../darwin/ios/framework/Source/spring_curve_ios.h | 4 ++-- .../darwin/ios/framework/Source/spring_curve_ios.mm | 4 +--- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index fb39ee05bfeef..6a87f72d29018 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -69,7 +69,6 @@ @interface FlutterViewController () Date: Thu, 8 Dec 2022 21:24:45 -0600 Subject: [PATCH 20/53] cleanup --- .../framework/Source/FlutterViewController.mm | 18 +++++++++--------- .../ios/framework/Source/spring_curve_ios.h | 5 +---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 6a87f72d29018..12f33094f1f12 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1393,10 +1393,16 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { animations:^{ // Set end value. [self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); + + // Set DisplayLink tracking values. + self.keyboardAnimationStartTime = fml::TimePoint().Now(); + self.keyboardAnimationFrom = _viewportMetrics.physical_view_inset_bottom; + self.keyboardAnimationTo = self.targetViewInsetBottom; + // Setup keyboard animation interpolation. CAAnimation* keyboardAnimation = [[self keyboardAnimationView].layer animationForKey:@"position"]; - [self setupKeyboardAnimationCurveIfNeeded:keyboardAnimation duration:duration]; + [self setupKeyboardAnimationCurveIfNeeded:keyboardAnimation]; } completion:^(BOOL finished) { if (_keyboardAnimationVSyncClient == currentVsyncClient) { @@ -1418,13 +1424,7 @@ - (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation { initWithStiffness:keyboardSpringAnimation.stiffness damping:keyboardSpringAnimation.damping mass:keyboardSpringAnimation.mass - initialVelocity:keyboardSpringAnimation.initialVelocity - settlingDuration:0.5]); - - // Set DisplayLink tracking values. - self.keyboardAnimationStartTime = fml::TimePoint().Now(); - self.keyboardAnimationFrom = _viewportMetrics.physical_view_inset_bottom; - self.keyboardAnimationTo = self.targetViewInsetBottom; + initialVelocity:keyboardSpringAnimation.initialVelocity]); } else { // Reset to use fallback keyboard animation tracking. _keyboardSpringCurve.reset(); @@ -1820,7 +1820,7 @@ - (BOOL)isAlwaysUse24HourFormat { // The brightness mode of the platform, e.g., light or dark, expressed as a string that // is understood by the Flutter framework. See the settings -// sysetupKeyboardAnimationCurveIfNeededstem channel for more information. +// system channel for more information. - (NSString*)brightnessMode { if (@available(iOS 13, *)) { UIUserInterfaceStyle style = self.traitCollection.userInterfaceStyle; diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index 58c50a3cac8f4..171363eef3362 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -14,12 +14,9 @@ mass:(double)mass initialVelocity:(double)initialVelocity - - (double)curveFunc - :(double)t; +- (double)curveFunc:(double)t; @property(nonatomic) double initialVelocity; -@property(nonatomic) double settlingDuration; -@property(nonatomic) double dampingRatio; @property(nonatomic) double omega; @end From c285ad901ba977ba94da845859ba823bc2c6d3b7 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 8 Dec 2022 21:25:59 -0600 Subject: [PATCH 21/53] cleanup --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- shell/platform/darwin/ios/framework/Source/spring_curve_ios.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 12f33094f1f12..8d79281ab45ef 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1393,7 +1393,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { animations:^{ // Set end value. [self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0); - + // Set DisplayLink tracking values. self.keyboardAnimationStartTime = fml::TimePoint().Now(); self.keyboardAnimationFrom = _viewportMetrics.physical_view_inset_bottom; diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index 171363eef3362..69843a57cad5e 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -14,7 +14,8 @@ mass:(double)mass initialVelocity:(double)initialVelocity -- (double)curveFunc:(double)t; + - (double)curveFunc + :(double)t; @property(nonatomic) double initialVelocity; @property(nonatomic) double omega; From 49abfc3fe567584be71d5e6b6cdf955623dff2e1 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 8 Dec 2022 21:38:09 -0600 Subject: [PATCH 22/53] update springCurveIos file --- .../platform/darwin/ios/framework/Source/spring_curve_ios.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index 69843a57cad5e..dcd6737067fd2 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -12,12 +12,12 @@ - (instancetype)initWithStiffness:(double)stiffness damping:(double)damping mass:(double)mass - initialVelocity:(double)initialVelocity + initialVelocity:(double)initialVelocity; - - (double)curveFunc - :(double)t; +- (double)curveFunc:(double)t; @property(nonatomic) double initialVelocity; +@property(nonatomic) double dampingRatio; @property(nonatomic) double omega; @end From 3cc8638cc095a1d92a9e18d726cdd5edd1753d08 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 12 Dec 2022 18:11:25 -0600 Subject: [PATCH 23/53] cleaning up --- .../framework/Source/FlutterViewController.mm | 19 +++++++------------ .../Source/FlutterViewControllerTest.mm | 3 +-- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 8d79281ab45ef..1631b44d0a66b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -69,8 +69,7 @@ @interface FlutterViewController () GetVsyncTargetTime() - flutterViewController.get().keyboardAnimationStartTime; double keyboardAnimationStop = diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 6ef7625f92e6c..4e6ce0bd567af 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -129,8 +129,7 @@ - (void)keyboardWillBeHidden:(NSNotification*)notification; - (void)startKeyBoardAnimation:(NSTimeInterval)duration; - (void)setupKeyboardAnimationVsyncClient; - (UIView*)keyboardAnimationView; -- (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation - duration:(double)duration; +- (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation; - (void)ensureViewportMetricsIsCorrect; - (void)invalidateKeyboardAnimationVSyncClient; - (void)addInternalPlugins; From 20ffdc983dba937223532f40b104d1e8ea3a6ff1 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 12 Dec 2022 20:45:44 -0600 Subject: [PATCH 24/53] simple optimizations --- .../ios/framework/Source/FlutterViewController.mm | 13 ++++++------- .../darwin/ios/framework/Source/spring_curve_ios.h | 6 +++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 1631b44d0a66b..079616d0b2030 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -66,8 +66,8 @@ @interface FlutterViewController () keyboardFrame.origin.y; + bool keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y; // Flag for simultaneous compounding animation calls. - self.keyboardAnimationIsCompounding = (self.keyboardAnimationIsShowing && keyboardIsShowing) || - (!self.keyboardAnimationIsShowing && !keyboardIsShowing); + self.keyboardAnimationIsCompounding = self.keyboardAnimationIsShowing == keyboardWillShow; - // Mark keyboard as showing or hiding - self.keyboardAnimationIsShowing = keyboardIsShowing; + // Mark keyboard as showing or hiding. + self.keyboardAnimationIsShowing = keyboardWillShow; [self startKeyBoardAnimation:duration]; } diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index dcd6737067fd2..052980c802e83 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -16,9 +16,9 @@ - (double)curveFunc:(double)t; -@property(nonatomic) double initialVelocity; -@property(nonatomic) double dampingRatio; -@property(nonatomic) double omega; +@property(nonatomic, assign) double initialVelocity; +@property(nonatomic, assign) double dampingRatio; +@property(nonatomic, assign) double omega; @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ From 1abe18da5cde49333ccbd4bb36bb60f4f77a7f06 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 12 Dec 2022 21:00:23 -0600 Subject: [PATCH 25/53] comment update --- .../darwin/ios/framework/Source/FlutterViewController.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 079616d0b2030..829c061f93bc4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1370,7 +1370,8 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { if ([self keyboardAnimationView].superview == nil) { [self.view addSubview:[self keyboardAnimationView]]; } else if (self.keyboardAnimationIsCompounding) { - // If keyboard animation is in the same direction as current animation, ignore it. + // If keyboardAnimationView has superview, means current animation is running, + // and if keyboard animation is in the same direction as current animation, ignore it. return; } From 3e5ef3f2be955b3ae039dfb499743023ded96220 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 12 Dec 2022 21:13:06 -0600 Subject: [PATCH 26/53] bool update --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 829c061f93bc4..459c4841e99d4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1317,7 +1317,7 @@ - (void)keyboardWillChangeFrame:(NSNotification*)notification { self.targetViewInsetBottom = 0; } - bool keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y; + BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y; // Flag for simultaneous compounding animation calls. self.keyboardAnimationIsCompounding = self.keyboardAnimationIsShowing == keyboardWillShow; From 328b166f816e1266154adb2add6d2a90273b1cc0 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Fri, 16 Dec 2022 10:38:44 -0500 Subject: [PATCH 27/53] credit spring calculation project --- shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index 13ea423fd6182..9ef1b3dfbf084 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -6,6 +6,8 @@ #include +// Spring calculation adapted from Pan Yusheng's research project. +// See: https://github.com/CosynPa/RevealSpringAnimation. @implementation KeyboardSpringCurve - (instancetype)initWithStiffness:(double)stiffness damping:(double)damping From 99f5d151c50fb924a3b85a7ddd418e27666b3d83 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 21 Dec 2022 13:41:34 -0600 Subject: [PATCH 28/53] improved setupKeyboardAnimationCureveIfNeeded tests Improved setupKeyboardAnimationCurveIfNeeded tests --- .../framework/Source/FlutterViewController.mm | 21 +++++----- .../Source/FlutterViewControllerTest.mm | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 2aa96e213227c..b5f8960e6a048 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1572,18 +1572,19 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { } - (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation { - // Set keyboard spring animation details (if animation is CASpringAnimation). - if ([keyboardAnimation isKindOfClass:[CASpringAnimation class]]) { - CASpringAnimation* keyboardSpringAnimation = (CASpringAnimation*)keyboardAnimation; - _keyboardSpringCurve.reset([[KeyboardSpringCurve alloc] - initWithStiffness:keyboardSpringAnimation.stiffness - damping:keyboardSpringAnimation.damping - mass:keyboardSpringAnimation.mass - initialVelocity:keyboardSpringAnimation.initialVelocity]); - } else { - // Reset to use fallback keyboard animation tracking. + // If keyboard animation is null or not a spring animation, fallback to DisplayLink tracking. + if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) { _keyboardSpringCurve.reset(); + return; } + + // Setup keyboard spring animation details for spring curve animation calculation. + CASpringAnimation* keyboardSpringAnimation = (CASpringAnimation*)keyboardAnimation; + _keyboardSpringCurve.reset([[KeyboardSpringCurve alloc] + initWithStiffness:keyboardSpringAnimation.stiffness + damping:keyboardSpringAnimation.damping + mass:keyboardSpringAnimation.mass + initialVelocity:keyboardSpringAnimation.initialVelocity]); } - (void)setupKeyboardAnimationVsyncClient { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 4c17876f55fb5..1dc2f741df924 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -16,6 +16,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h" #import "flutter/shell/platform/embedder/embedder.h" FLUTTER_ASSERT_ARC @@ -134,6 +135,7 @@ - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGR - (void)startKeyBoardAnimation:(NSTimeInterval)duration; - (void)setupKeyboardAnimationVsyncClient; - (UIView*)keyboardAnimationView; +- (KeyboardSpringCurve*)keyboardSpringCurve; - (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation; - (void)ensureViewportMetricsIsCorrect; - (void)invalidateKeyboardAnimationVSyncClient; @@ -208,6 +210,43 @@ - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationCurveIfNeeded OCMVerify([viewControllerMock setupKeyboardAnimationCurveIfNeeded:keyboardAnimation]); } +- (void)testSetupKeyboardAnimationCurveIfNeeded { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + + // Null check. + [viewControllerMock setupKeyboardAnimationCurveIfNeeded:nil]; + KeyboardSpringCurve* keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; + XCTAssertTrue(keyboardSpringCurve == nil); + + // CAAnimation that is not CASpringAnimation. + CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation]; + nonSpringAnimation.duration = 1.0; + nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0]; + nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0]; + nonSpringAnimation.keyPath = @"position"; + [viewControllerMock setupKeyboardAnimationCurveIfNeeded:nonSpringAnimation]; + keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; + + XCTAssertTrue(keyboardSpringCurve == nil); + + // CASpringAnimation. + CASpringAnimation* springAnimation = [CASpringAnimation animation]; + springAnimation.mass = 1.0; + springAnimation.stiffness = 100.0; + springAnimation.damping = 10.0; + springAnimation.keyPath = @"position"; + springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; + springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; + [viewControllerMock setupKeyboardAnimationCurveIfNeeded:springAnimation]; + keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; + XCTAssertTrue(keyboardSpringCurve != nil); +} + - (void)testShouldIgnoreKeyboardNotification { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; From 03c1f756806354e8b3209caa4a5f622c784a0884 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 21 Dec 2022 15:41:20 -0600 Subject: [PATCH 29/53] Optimized compounding animation checks and added new tests --- .../framework/Source/FlutterViewController.mm | 21 ++--- .../Source/FlutterViewControllerTest.mm | 90 ++++++++++++++++++- 2 files changed, 100 insertions(+), 11 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index b5f8960e6a048..d8bfa27b5983a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -67,7 +67,6 @@ @interface FlutterViewController () keyboardFrame.origin.y; - // Flag for simultaneous compounding animation calls. - self.keyboardAnimationIsCompounding = self.keyboardAnimationIsShowing == keyboardWillShow; + // This captures animation calls made while the keyboard animation is currently animating. If the + // new animation is in the same direction as the current animation, this flag lets the current + // animation continue with an updated targetViewInsetBottom instead of starting a new keyboard + // animation. This allows for smoother keyboard animation interpolation. + BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y; + BOOL keyboardAnimationIsCompounding = + self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil; // Mark keyboard as showing or hiding. self.keyboardAnimationIsShowing = keyboardWillShow; - [self startKeyBoardAnimation:duration]; + if (!keyboardAnimationIsCompounding) { + [self startKeyBoardAnimation:duration]; + } } - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification { @@ -1529,10 +1534,6 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { if ([self keyboardAnimationView].superview == nil) { [self.view addSubview:[self keyboardAnimationView]]; - } else if (self.keyboardAnimationIsCompounding) { - // If keyboardAnimationView has superview, means current animation is running, - // and if keyboard animation is in the same direction as current animation, ignore it. - return; } // Remove running animation when start another animation. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 1dc2f741df924..de149555c1d06 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -115,6 +115,7 @@ @interface FlutterViewController (Tests) @property(nonatomic, assign) double targetViewInsetBottom; @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground; +@property(nonatomic, assign) BOOL keyboardAnimationIsShowing; - (void)createTouchRateCorrectionVSyncClientIfNeeded; - (void)surfaceUpdated:(BOOL)appeared; @@ -223,7 +224,7 @@ - (void)testSetupKeyboardAnimationCurveIfNeeded { KeyboardSpringCurve* keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; XCTAssertTrue(keyboardSpringCurve == nil); - // CAAnimation that is not CASpringAnimation. + // CAAnimation that is not a CASpringAnimation. CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation]; nonSpringAnimation.duration = 1.0; nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0]; @@ -247,6 +248,92 @@ - (void)testSetupKeyboardAnimationCurveIfNeeded { XCTAssertTrue(keyboardSpringCurve != nil); } +- (void)testKeyboardAnimationIsShowingAndCompounding { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + + BOOL isLocal = YES; + CGFloat screenHeight = UIScreen.mainScreen.bounds.size.height; + + // Start show keyboard animation. + CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, 0, 0); + CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, 0, 0); + NSNotification* fakeNotification = [NSNotification + notificationWithName:UIKeyboardWillHideNotification + object:nil + userInfo:@{ + @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame), + @"UIKeyboardFrameEndUserInfoKey" : @(initialShowKeyboardEndFrame), + @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25), + @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) + }]; + viewControllerMock.targetViewInsetBottom = 100; + [viewControllerMock handleKeyboardNotification:fakeNotification]; + BOOL isShowingAnimation1 = [viewControllerMock keyboardAnimationIsShowing]; + XCTAssertTrue(isShowingAnimation1 == YES); + + // Start compounding show keyboard animation. + CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, 0, 0); + CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, 0, 0); + fakeNotification = [NSNotification + notificationWithName:UIKeyboardWillHideNotification + object:nil + userInfo:@{ + @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame), + @"UIKeyboardFrameEndUserInfoKey" : @(compoundingShowKeyboardEndFrame), + @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25), + @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) + }]; + + viewControllerMock.targetViewInsetBottom = 200; + [viewControllerMock handleKeyboardNotification:fakeNotification]; + BOOL isShowingAnimation2 = [viewControllerMock keyboardAnimationIsShowing]; + XCTAssertTrue(isShowingAnimation2 == YES); + XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2); + + // Start hide keyboard animation. + CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, 0, 0); + CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, 0, 0); + fakeNotification = [NSNotification + notificationWithName:UIKeyboardWillHideNotification + object:nil + userInfo:@{ + @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame), + @"UIKeyboardFrameEndUserInfoKey" : @(initialHideKeyboardEndFrame), + @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25), + @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) + }]; + + viewControllerMock.targetViewInsetBottom = 100; + [viewControllerMock handleKeyboardNotification:fakeNotification]; + BOOL isShowingAnimation3 = [viewControllerMock keyboardAnimationIsShowing]; + XCTAssertTrue(isShowingAnimation3 == NO); + XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3); + + // Start compounding hide keyboard animation. + CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, 0, 0); + CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, 0, 0); + fakeNotification = [NSNotification + notificationWithName:UIKeyboardWillHideNotification + object:nil + userInfo:@{ + @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame), + @"UIKeyboardFrameEndUserInfoKey" : @(compoundingHideKeyboardEndFrame), + @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25), + @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) + }]; + + viewControllerMock.targetViewInsetBottom = 0; + [viewControllerMock handleKeyboardNotification:fakeNotification]; + BOOL isShowingAnimation4 = [viewControllerMock keyboardAnimationIsShowing]; + XCTAssertTrue(isShowingAnimation4 == NO); + XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4); +} + - (void)testShouldIgnoreKeyboardNotification { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; @@ -272,6 +359,7 @@ - (void)testShouldIgnoreKeyboardNotification { @"UIKeyboardAnimationDurationUserInfoKey" : @0.25, @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) }]; + BOOL shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification]; XCTAssertTrue(shouldIgnore == NO); From c03d64247d767dd0306d7f3052ee60d718d49550 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 21 Dec 2022 22:40:39 -0600 Subject: [PATCH 30/53] changed viewport update logic --- .../framework/Source/FlutterViewController.mm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index d8bfa27b5983a..63239aaad8ab3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1605,10 +1605,13 @@ - (void)setupKeyboardAnimationVsyncClient { [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; } - CGFloat newY; if ([flutterViewController keyboardSpringCurve] == nil) { - newY = - flutterViewController.get().keyboardAnimationView.layer.presentationLayer.frame.origin.y; + if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) { + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = + flutterViewController.get() + .keyboardAnimationView.layer.presentationLayer.frame.origin.y; + [flutterViewController updateViewportMetrics]; + } } else { double start = flutterViewController.get().originalViewInsetBottom; double end = flutterViewController.get().targetViewInsetBottom; @@ -1616,11 +1619,10 @@ - (void)setupKeyboardAnimationVsyncClient { flutterViewController.get().keyboardAnimationStartTime; double keyboardAnimationStop = [[flutterViewController keyboardSpringCurve] curveFunc:timeElapsed.ToSecondsF()]; - newY = start + (end - start) * keyboardAnimationStop; + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = + start + (end - start) * keyboardAnimationStop; + [flutterViewController updateViewportMetrics]; } - - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; - [flutterViewController updateViewportMetrics]; }; flutter::Shell& shell = [_engine.get() shell]; NSAssert(_keyboardAnimationVSyncClient == nil, From fd2eaee777a4227dc8a01cde605d2e572dd02569 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 2 Jan 2023 16:17:57 -0600 Subject: [PATCH 31/53] Update FlutterViewControllerTest.mm Updated test nits --- .../Source/FlutterViewControllerTest.mm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index de149555c1d06..ddca601dd3576 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -273,8 +273,8 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { }]; viewControllerMock.targetViewInsetBottom = 100; [viewControllerMock handleKeyboardNotification:fakeNotification]; - BOOL isShowingAnimation1 = [viewControllerMock keyboardAnimationIsShowing]; - XCTAssertTrue(isShowingAnimation1 == YES); + BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing; + XCTAssertTrue(isShowingAnimation1); // Start compounding show keyboard animation. CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, 0, 0); @@ -291,8 +291,8 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { viewControllerMock.targetViewInsetBottom = 200; [viewControllerMock handleKeyboardNotification:fakeNotification]; - BOOL isShowingAnimation2 = [viewControllerMock keyboardAnimationIsShowing]; - XCTAssertTrue(isShowingAnimation2 == YES); + BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing; + XCTAssertTrue(isShowingAnimation2); XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2); // Start hide keyboard animation. @@ -310,8 +310,8 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { viewControllerMock.targetViewInsetBottom = 100; [viewControllerMock handleKeyboardNotification:fakeNotification]; - BOOL isShowingAnimation3 = [viewControllerMock keyboardAnimationIsShowing]; - XCTAssertTrue(isShowingAnimation3 == NO); + BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing; + XCTAssertFalse(isShowingAnimation3); XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3); // Start compounding hide keyboard animation. @@ -329,8 +329,8 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { viewControllerMock.targetViewInsetBottom = 0; [viewControllerMock handleKeyboardNotification:fakeNotification]; - BOOL isShowingAnimation4 = [viewControllerMock keyboardAnimationIsShowing]; - XCTAssertTrue(isShowingAnimation4 == NO); + BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing; + XCTAssertFalse(isShowingAnimation4); XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4); } From 33ca10660d299e333c9fd4c616514f4d47ea3efb Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Mon, 2 Jan 2023 16:27:27 -0600 Subject: [PATCH 32/53] Updated license --- ci/licenses_golden/licenses_flutter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a8b517fdbf920..889e2aab9e365 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2533,6 +2533,8 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_mes ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/ios_context.h + ../../../flutter/LICENSE From 934bcf8d3aeb8f2bbe4141747e400ee75e90e601 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Tue, 3 Jan 2023 17:42:55 -0600 Subject: [PATCH 33/53] Updated tests --- .../framework/Source/FlutterViewController.mm | 2 +- .../Source/FlutterViewControllerTest.mm | 72 +++++++++++-------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 63239aaad8ab3..36da0c73037c7 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1330,7 +1330,7 @@ - (void)handleKeyboardNotification:(NSNotification*)notification { } NSDictionary* info = notification.userInfo; - CGRect beginKeyboardFrame = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; + CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue]; FlutterKeyboardMode keyboardMode = [self calculateKeyboardAttachMode:notification]; CGFloat calculatedInset = [self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index ddca601dd3576..0a0e19a04f41a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -171,6 +171,19 @@ - (void)tearDown { self.messageSent = nil; } +- (id)setupMockMainScreenAndView:(FlutterViewController*)viewControllerMock + viewFrame:(CGRect)viewFrame + convertedFrame:(CGRect)convertedFrame { + OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); + id mockView = OCMClassMock([UIView class]); + OCMStub([mockView frame]).andReturn(viewFrame); + OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]]) + .andReturn(convertedFrame); + OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView); + + return mockView; +} + - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; @@ -218,6 +231,8 @@ - (void)testSetupKeyboardAnimationCurveIfNeeded { nibName:nil bundle:nil]; FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + CGRect viewFrame = UIScreen.mainScreen.bounds; + [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame]; // Null check. [viewControllerMock setupKeyboardAnimationCurveIfNeeded:nil]; @@ -255,15 +270,18 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { nibName:nil bundle:nil]; FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + CGRect viewFrame = UIScreen.mainScreen.bounds; + [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame]; BOOL isLocal = YES; CGFloat screenHeight = UIScreen.mainScreen.bounds.size.height; + CGFloat screenWidth = UIScreen.mainScreen.bounds.size.height; // Start show keyboard animation. - CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, 0, 0); - CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, 0, 0); + CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, screenWidth, 250); + CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500); NSNotification* fakeNotification = [NSNotification - notificationWithName:UIKeyboardWillHideNotification + notificationWithName:UIKeyboardWillChangeFrameNotification object:nil userInfo:@{ @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame), @@ -271,16 +289,16 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25), @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) }]; - viewControllerMock.targetViewInsetBottom = 100; + viewControllerMock.targetViewInsetBottom = 0; [viewControllerMock handleKeyboardNotification:fakeNotification]; BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing; XCTAssertTrue(isShowingAnimation1); // Start compounding show keyboard animation. - CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, 0, 0); - CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, 0, 0); + CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250); + CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, screenWidth, 500); fakeNotification = [NSNotification - notificationWithName:UIKeyboardWillHideNotification + notificationWithName:UIKeyboardWillChangeFrameNotification object:nil userInfo:@{ @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame), @@ -289,17 +307,16 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) }]; - viewControllerMock.targetViewInsetBottom = 200; [viewControllerMock handleKeyboardNotification:fakeNotification]; BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing; XCTAssertTrue(isShowingAnimation2); XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2); // Start hide keyboard animation. - CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, 0, 0); - CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, 0, 0); + CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, screenWidth, 250); + CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500); fakeNotification = [NSNotification - notificationWithName:UIKeyboardWillHideNotification + notificationWithName:UIKeyboardWillChangeFrameNotification object:nil userInfo:@{ @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame), @@ -308,17 +325,16 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) }]; - viewControllerMock.targetViewInsetBottom = 100; [viewControllerMock handleKeyboardNotification:fakeNotification]; BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing; XCTAssertFalse(isShowingAnimation3); XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3); // Start compounding hide keyboard animation. - CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, 0, 0); - CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, 0, 0); + CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250); + CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, screenWidth, 500); fakeNotification = [NSNotification - notificationWithName:UIKeyboardWillHideNotification + notificationWithName:UIKeyboardWillChangeFrameNotification object:nil userInfo:@{ @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame), @@ -327,7 +343,6 @@ - (void)testKeyboardAnimationIsShowingAndCompounding { @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) }]; - viewControllerMock.targetViewInsetBottom = 0; [viewControllerMock handleKeyboardNotification:fakeNotification]; BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing; XCTAssertFalse(isShowingAnimation4); @@ -341,7 +356,8 @@ - (void)testShouldIgnoreKeyboardNotification { nibName:nil bundle:nil]; FlutterViewController* viewControllerMock = OCMPartialMock(viewController); - OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); + CGRect viewFrame = UIScreen.mainScreen.bounds; + [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame]; CGFloat screenWidth = UIScreen.mainScreen.bounds.size.width; CGFloat screenHeight = UIScreen.mainScreen.bounds.size.height; @@ -442,12 +458,12 @@ - (void)testCalculateKeyboardAttachMode { bundle:nil]; FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + CGRect viewFrame = UIScreen.mainScreen.bounds; + [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame]; CGFloat screenWidth = UIScreen.mainScreen.bounds.size.width; CGFloat screenHeight = UIScreen.mainScreen.bounds.size.height; - OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); - // hide notification CGRect keyboardFrame = CGRectZero; NSNotification* notification = @@ -574,17 +590,14 @@ - (void)testCalculateMultitaskingAdjustment { CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40); CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40); CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300); - - id mockView = OCMClassMock([UIView class]); - OCMStub([mockView frame]).andReturn(viewOrigFrame); - OCMStub([mockView convertRect:viewOrigFrame toCoordinateSpace:[OCMArg any]]) - .andReturn(convertedViewFrame); + id mockView = [self setupMockMainScreenAndView:viewControllerMock + viewFrame:viewOrigFrame + convertedFrame:convertedViewFrame]; id mockTraitCollection = OCMClassMock([UITraitCollection class]); OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad); OCMStub([mockTraitCollection horizontalSizeClass]).andReturn(UIUserInterfaceSizeClassCompact); OCMStub([mockTraitCollection verticalSizeClass]).andReturn(UIUserInterfaceSizeClassRegular); OCMStub([mockView traitCollection]).andReturn(mockTraitCollection); - OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView); CGFloat adjustment = [viewControllerMock calculateMultitaskingAdjustment:screenRect keyboardFrame:keyboardFrame]; @@ -606,6 +619,9 @@ - (void)testCalculateKeyboardInset { CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40); CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300); + [self setupMockMainScreenAndView:viewControllerMock + viewFrame:viewOrigFrame + convertedFrame:convertedViewFrame]; id mockView = OCMClassMock([UIView class]); OCMStub([mockView frame]).andReturn(viewOrigFrame); OCMStub([mockView convertRect:viewOrigFrame toCoordinateSpace:[OCMArg any]]) @@ -638,11 +654,7 @@ - (void)testHandleKeyboardNotification { @"UIKeyboardIsLocalUserInfoKey" : @(isLocal) }]; FlutterViewController* viewControllerMock = OCMPartialMock(viewController); - OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); - id mockView = OCMClassMock([UIView class]); - OCMStub([mockView frame]).andReturn(viewFrame); - OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]]).andReturn(viewFrame); - OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView); + [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame]; viewControllerMock.targetViewInsetBottom = 0; XCTestExpectation* expectation = [self expectationWithDescription:@"update viewport"]; OCMStub([mockEngine updateViewportMetrics:flutter::ViewportMetrics()]) From 76245e7d19eea3ad685decd6ef0288be1c17c813 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 10:50:12 -0600 Subject: [PATCH 34/53] Cleanup --- .../ios/framework/Source/FlutterViewControllerTest.mm | 6 ------ 1 file changed, 6 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 0a0e19a04f41a..46ddece83695a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -582,7 +582,6 @@ - (void)testCalculateMultitaskingAdjustment { nibName:nil bundle:nil]; FlutterViewController* viewControllerMock = OCMPartialMock(viewController); - OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); CGFloat screenWidth = UIScreen.mainScreen.bounds.size.width; CGFloat screenHeight = UIScreen.mainScreen.bounds.size.height; @@ -622,11 +621,6 @@ - (void)testCalculateKeyboardInset { [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewOrigFrame convertedFrame:convertedViewFrame]; - id mockView = OCMClassMock([UIView class]); - OCMStub([mockView frame]).andReturn(viewOrigFrame); - OCMStub([mockView convertRect:viewOrigFrame toCoordinateSpace:[OCMArg any]]) - .andReturn(convertedViewFrame); - OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView); CGFloat inset = [viewControllerMock calculateKeyboardInset:keyboardFrame keyboardMode:FlutterKeyboardModeDocked]; From 73062e734c8593015e007d069574764bc925d5c7 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 14:14:25 -0600 Subject: [PATCH 35/53] Migrated spring class based on React --- .../ios/framework/Source/spring_curve_ios.h | 5 +- .../ios/framework/Source/spring_curve_ios.mm | 46 +++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index 052980c802e83..334e59aa050ba 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -16,9 +16,10 @@ - (double)curveFunc:(double)t; +@property(nonatomic, assign) double stiffness; +@property(nonatomic, assign) double damping; +@property(nonatomic, assign) double mass; @property(nonatomic, assign) double initialVelocity; -@property(nonatomic, assign) double dampingRatio; -@property(nonatomic, assign) double omega; @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index 9ef1b3dfbf084..52fff06db2769 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -6,8 +6,14 @@ #include -// Spring calculation adapted from Pan Yusheng's research project. -// See: https://github.com/CosynPa/RevealSpringAnimation. +// This simplified spring model is based off of a damped harmonic oscillator. +// See: https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator +// +// This models the closed form of the second order differential equation which happens to match the +// algorithm used by CASpringAnimation, a QuartzCore (iOS) API that creates spring animations. +// +// Spring code adapted from React Native's Animation Library, see: +// https://github.com/facebook/react-native/blob/main/Libraries/Animated/animations/SpringAnimation.js @implementation KeyboardSpringCurve - (instancetype)initWithStiffness:(double)stiffness damping:(double)damping @@ -15,39 +21,31 @@ - (instancetype)initWithStiffness:(double)stiffness initialVelocity:(double)initialVelocity { self = [super init]; if (self) { - _dampingRatio = MIN(1.0, damping / 2 / sqrt(stiffness * mass)); + _stiffness = stiffness; + _damping = damping; + _mass = mass; _initialVelocity = initialVelocity; - - double response = MAX(1e-5, 2 * M_PI / sqrt(stiffness / mass)); - _omega = 2 * M_PI / response; } return self; } - (double)curveFunc:(double)t { + double zeta = self.damping / (2 * sqrt(self.stiffness * self.mass)); // damping ratio + double omega0 = sqrt(self.stiffness / self.mass); // undamped angular frequency of the oscillator + double omega1 = omega0 * sqrt(1.0 - (zeta * zeta)); // exponential decay double v0 = self.initialVelocity; - double zeta = self.dampingRatio; double y; - if (abs(zeta - 1.0) < 1e-8) { - double c1 = -1.0; - double c2 = v0 - self.omega; - y = (c1 + c2 * t) * exp(-self.omega * t); - } else if (zeta > 1) { - double s1 = self.omega * (-zeta + sqrt(zeta * zeta - 1)); - double s2 = self.omega * (-zeta - sqrt(zeta * zeta - 1)); - double c1 = (-s2 - v0) / (s2 - s1); - double c2 = (s1 + v0) / (s2 - s1); - y = c1 * exp(s1 * t) + c2 * exp(s2 * t); + if (zeta < 1) { + // Under damped + double envelope = exp(-zeta * omega0 * t); + y = 1 - envelope * ((v0 + zeta * omega0) / omega1 * sin(omega1 * t) + cos(omega1 * t)); } else { - double a = -self.omega * zeta; - double b = self.omega * sqrt(1 - zeta * zeta); - double c2 = (v0 + a) / b; - double theta = atan(c2); - // Alternatively y = (-cos(b * t) + c2 * sin(b * t)) * exp(a * t) - y = sqrt(1 + c2 * c2) * exp(a * t) * cos(b * t + theta + M_PI); + // Critically damped spring + double envelope = exp(-omega0 * t); + y = 1 - envelope * (1 + (v0 + omega0) * t); } - return y + 1; + return y; } @end From b2a92dd6761cdba2aae94a69699be0e974b9be28 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 14:55:50 -0600 Subject: [PATCH 36/53] Updated syntax --- .../darwin/ios/framework/Source/spring_curve_ios.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index 52fff06db2769..64b55b2a5c3eb 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -21,10 +21,10 @@ - (instancetype)initWithStiffness:(double)stiffness initialVelocity:(double)initialVelocity { self = [super init]; if (self) { - _stiffness = stiffness; - _damping = damping; - _mass = mass; - _initialVelocity = initialVelocity; + self.stiffness = stiffness; + self.damping = damping; + self.mass = mass; + self.initialVelocity = initialVelocity; } return self; } From 61b70f68c48e600d65dec3f8419f58a6e5d43c2f Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 15:08:39 -0600 Subject: [PATCH 37/53] Update shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm Co-authored-by: Jenn Magder --- .../darwin/ios/framework/Source/spring_curve_ios.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index 64b55b2a5c3eb..52fff06db2769 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -21,10 +21,10 @@ - (instancetype)initWithStiffness:(double)stiffness initialVelocity:(double)initialVelocity { self = [super init]; if (self) { - self.stiffness = stiffness; - self.damping = damping; - self.mass = mass; - self.initialVelocity = initialVelocity; + _stiffness = stiffness; + _damping = damping; + _mass = mass; + _initialVelocity = initialVelocity; } return self; } From 059bb70c35517c7baaeb3b269fb38d9dd3f7c3f1 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 15:08:48 -0600 Subject: [PATCH 38/53] Update shell/platform/darwin/ios/framework/Source/spring_curve_ios.h Co-authored-by: Jenn Magder --- shell/platform/darwin/ios/framework/Source/spring_curve_ios.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index 334e59aa050ba..053cef665ec15 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -7,7 +7,7 @@ #include -@interface KeyboardSpringCurve : NSObject +@interface FlutterKeyboardSpringCurve : NSObject - (instancetype)initWithStiffness:(double)stiffness damping:(double)damping From 9c786791ac60399504649119d9a519d7270a0a4e Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 15:13:18 -0600 Subject: [PATCH 39/53] Updated naming --- .../darwin/ios/framework/Source/FlutterViewController.mm | 8 ++++---- .../ios/framework/Source/FlutterViewControllerTest.mm | 4 ++-- .../darwin/ios/framework/Source/spring_curve_ios.mm | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 36da0c73037c7..493f64d7af785 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -127,7 +127,7 @@ @implementation FlutterViewController { // https://github.com/flutter/flutter/issues/35050 fml::scoped_nsobject _scrollView; fml::scoped_nsobject _keyboardAnimationView; - fml::scoped_nsobject _keyboardSpringCurve; + fml::scoped_nsobject _keyboardSpringCurve; MouseState _mouseState; // Timestamp after which a scroll inertia cancel event should be inferred. NSTimeInterval _scrollInertiaEventStartline; @@ -599,7 +599,7 @@ - (UIView*)keyboardAnimationView { return _keyboardAnimationView.get(); } -- (KeyboardSpringCurve*)keyboardSpringCurve { +- (FlutterKeyboardSpringCurve*)keyboardSpringCurve { return _keyboardSpringCurve.get(); } @@ -1578,10 +1578,10 @@ - (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation { _keyboardSpringCurve.reset(); return; } - + // Setup keyboard spring animation details for spring curve animation calculation. CASpringAnimation* keyboardSpringAnimation = (CASpringAnimation*)keyboardAnimation; - _keyboardSpringCurve.reset([[KeyboardSpringCurve alloc] + _keyboardSpringCurve.reset([[FlutterKeyboardSpringCurve alloc] initWithStiffness:keyboardSpringAnimation.stiffness damping:keyboardSpringAnimation.damping mass:keyboardSpringAnimation.mass diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 46ddece83695a..4101111ed5f94 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -136,7 +136,7 @@ - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGR - (void)startKeyBoardAnimation:(NSTimeInterval)duration; - (void)setupKeyboardAnimationVsyncClient; - (UIView*)keyboardAnimationView; -- (KeyboardSpringCurve*)keyboardSpringCurve; +- (FlutterKeyboardSpringCurve*)keyboardSpringCurve; - (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation; - (void)ensureViewportMetricsIsCorrect; - (void)invalidateKeyboardAnimationVSyncClient; @@ -236,7 +236,7 @@ - (void)testSetupKeyboardAnimationCurveIfNeeded { // Null check. [viewControllerMock setupKeyboardAnimationCurveIfNeeded:nil]; - KeyboardSpringCurve* keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; + FlutterKeyboardSpringCurve* keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; XCTAssertTrue(keyboardSpringCurve == nil); // CAAnimation that is not a CASpringAnimation. diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index 52fff06db2769..aed01bd5e9d05 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -14,7 +14,7 @@ // // Spring code adapted from React Native's Animation Library, see: // https://github.com/facebook/react-native/blob/main/Libraries/Animated/animations/SpringAnimation.js -@implementation KeyboardSpringCurve +@implementation FlutterKeyboardSpringCurve - (instancetype)initWithStiffness:(double)stiffness damping:(double)damping mass:(double)mass From ebcb72290840f514867102d188cf8ab3517d8197 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 15:13:45 -0600 Subject: [PATCH 40/53] Updated naming --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 493f64d7af785..423a3348eff4d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1578,7 +1578,7 @@ - (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation { _keyboardSpringCurve.reset(); return; } - + // Setup keyboard spring animation details for spring curve animation calculation. CASpringAnimation* keyboardSpringAnimation = (CASpringAnimation*)keyboardAnimation; _keyboardSpringCurve.reset([[FlutterKeyboardSpringCurve alloc] From 3728335842a23d0d931199a65891209062828d5e Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 16:08:37 -0600 Subject: [PATCH 41/53] Optimized spring model calculations --- .../ios/framework/Source/spring_curve_ios.h | 6 ++--- .../ios/framework/Source/spring_curve_ios.mm | 23 ++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index 053cef665ec15..f1fa437b674e1 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -16,10 +16,10 @@ - (double)curveFunc:(double)t; -@property(nonatomic, assign) double stiffness; -@property(nonatomic, assign) double damping; -@property(nonatomic, assign) double mass; @property(nonatomic, assign) double initialVelocity; +@property(nonatomic, assign) double zeta; +@property(nonatomic, assign) double omega0; +@property(nonatomic, assign) double omega1; @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index aed01bd5e9d05..df78d85e33683 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -21,29 +21,26 @@ - (instancetype)initWithStiffness:(double)stiffness initialVelocity:(double)initialVelocity { self = [super init]; if (self) { - _stiffness = stiffness; - _damping = damping; - _mass = mass; _initialVelocity = initialVelocity; + _zeta = damping / (2 * sqrt(stiffness * mass)); // damping ratio + _omega0 = sqrt(stiffness / mass); // undamped angular frequency of the oscillator + _omega1 = self.omega0 * sqrt(1.0 - (self.zeta * self.zeta)); // exponential decay } return self; } - (double)curveFunc:(double)t { - double zeta = self.damping / (2 * sqrt(self.stiffness * self.mass)); // damping ratio - double omega0 = sqrt(self.stiffness / self.mass); // undamped angular frequency of the oscillator - double omega1 = omega0 * sqrt(1.0 - (zeta * zeta)); // exponential decay - double v0 = self.initialVelocity; - double y; - if (zeta < 1) { + if (self.zeta < 1) { // Under damped - double envelope = exp(-zeta * omega0 * t); - y = 1 - envelope * ((v0 + zeta * omega0) / omega1 * sin(omega1 * t) + cos(omega1 * t)); + double envelope = exp(-self.zeta * self.omega0 * t); + y = 1 - envelope * ((self.initialVelocity + self.zeta * self.omega0) / self.omega1 * + sin(self.omega1 * t) + + cos(self.omega1 * t)); } else { // Critically damped spring - double envelope = exp(-omega0 * t); - y = 1 - envelope * (1 + (v0 + omega0) * t); + double envelope = exp(-self.omega0 * t); + y = 1 - envelope * (1 + (self.initialVelocity + self.omega0) * t); } return y; From 6d025129b620db44741f70d659900430d9536628 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 20:25:03 -0600 Subject: [PATCH 42/53] Improved interpolation accuracy --- .../framework/Source/FlutterViewController.mm | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 423a3348eff4d..2171546d8becf 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1356,6 +1356,11 @@ - (void)handleKeyboardNotification:(NSNotification*)notification { self.keyboardAnimationIsShowing = keyboardWillShow; if (!keyboardAnimationIsCompounding) { + // Match the begin frame of the keyboard notification to ensure spring interpolation accuracy. + self.originalViewInsetBottom = + ([self mainScreenIfViewLoaded].bounds.size.height - beginKeyboardFrame.origin.y) * + [self mainScreenIfViewLoaded].scale; + [self startKeyBoardAnimation:duration]; } } @@ -1524,7 +1529,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { return; } - // When call this method first time, + // When this method is called for the first time, // initialize the keyboardAnimationView to get animation interpolation during animation. if ([self keyboardAnimationView] == nil) { UIView* keyboardAnimationView = [[UIView alloc] init]; @@ -1543,7 +1548,6 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { [self keyboardAnimationView].frame = CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0); self.keyboardAnimationStartTime = fml::TimePoint().Now(); - self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom; // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; @@ -1619,8 +1623,23 @@ - (void)setupKeyboardAnimationVsyncClient { flutterViewController.get().keyboardAnimationStartTime; double keyboardAnimationStop = [[flutterViewController keyboardSpringCurve] curveFunc:timeElapsed.ToSecondsF()]; - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - start + (end - start) * keyboardAnimationStop; + double newY = start + (end - start) * keyboardAnimationStop; + + // When a running animation is interrupted with the opposite animation (e.g. show interrupted + // with hide), adjust the spring curve until the model catches up with the current + // bottomInset. + double currentViewInsetBottom = + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom; + double keyboardSpringFrameAdjustment = 0; + while ((start > end && currentViewInsetBottom < newY) || + (end > start && currentViewInsetBottom > newY)) { + keyboardSpringFrameAdjustment += 1.0 / [DisplayLinkManager displayRefreshRate]; + keyboardAnimationStop = [[flutterViewController keyboardSpringCurve] + curveFunc:timeElapsed.ToSecondsF() + keyboardSpringFrameAdjustment]; + newY = start + (end - start) * keyboardAnimationStop; + } + + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; [flutterViewController updateViewportMetrics]; } }; From 3530db3d629cfb0648a81946be94ea1869a725e7 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 21:04:06 -0600 Subject: [PATCH 43/53] Revert "Improved interpolation accuracy" This reverts commit 6d025129b620db44741f70d659900430d9536628. --- .../framework/Source/FlutterViewController.mm | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 2171546d8becf..423a3348eff4d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1356,11 +1356,6 @@ - (void)handleKeyboardNotification:(NSNotification*)notification { self.keyboardAnimationIsShowing = keyboardWillShow; if (!keyboardAnimationIsCompounding) { - // Match the begin frame of the keyboard notification to ensure spring interpolation accuracy. - self.originalViewInsetBottom = - ([self mainScreenIfViewLoaded].bounds.size.height - beginKeyboardFrame.origin.y) * - [self mainScreenIfViewLoaded].scale; - [self startKeyBoardAnimation:duration]; } } @@ -1529,7 +1524,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { return; } - // When this method is called for the first time, + // When call this method first time, // initialize the keyboardAnimationView to get animation interpolation during animation. if ([self keyboardAnimationView] == nil) { UIView* keyboardAnimationView = [[UIView alloc] init]; @@ -1548,6 +1543,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { [self keyboardAnimationView].frame = CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0); self.keyboardAnimationStartTime = fml::TimePoint().Now(); + self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom; // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; @@ -1623,23 +1619,8 @@ - (void)setupKeyboardAnimationVsyncClient { flutterViewController.get().keyboardAnimationStartTime; double keyboardAnimationStop = [[flutterViewController keyboardSpringCurve] curveFunc:timeElapsed.ToSecondsF()]; - double newY = start + (end - start) * keyboardAnimationStop; - - // When a running animation is interrupted with the opposite animation (e.g. show interrupted - // with hide), adjust the spring curve until the model catches up with the current - // bottomInset. - double currentViewInsetBottom = - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom; - double keyboardSpringFrameAdjustment = 0; - while ((start > end && currentViewInsetBottom < newY) || - (end > start && currentViewInsetBottom > newY)) { - keyboardSpringFrameAdjustment += 1.0 / [DisplayLinkManager displayRefreshRate]; - keyboardAnimationStop = [[flutterViewController keyboardSpringCurve] - curveFunc:timeElapsed.ToSecondsF() + keyboardSpringFrameAdjustment]; - newY = start + (end - start) * keyboardAnimationStop; - } - - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = newY; + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = + start + (end - start) * keyboardAnimationStop; [flutterViewController updateViewportMetrics]; } }; From fb497a4e55166a7e64de5831d9c7f43b1f8658c0 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 21:05:12 -0600 Subject: [PATCH 44/53] Updated comment --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 423a3348eff4d..ad5e8814ada04 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1524,7 +1524,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { return; } - // When call this method first time, + // When this method is called for the first time, // initialize the keyboardAnimationView to get animation interpolation during animation. if ([self keyboardAnimationView] == nil) { UIView* keyboardAnimationView = [[UIView alloc] init]; From 3dcb341b9ec2d8f21667f77260b20afb1a45abba Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Wed, 4 Jan 2023 21:13:06 -0600 Subject: [PATCH 45/53] Updated spring curve comments --- .../platform/darwin/ios/framework/Source/spring_curve_ios.h | 5 +++++ .../darwin/ios/framework/Source/spring_curve_ios.mm | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h index f1fa437b674e1..225588cd31ca9 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h @@ -7,6 +7,11 @@ #include +// This simplified spring model is based off of a damped harmonic oscillator. +// See: https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator +// +// This models the closed form of the second order differential equation which happens to match the +// algorithm used by CASpringAnimation, a QuartzCore (iOS) API that creates spring animations. @interface FlutterKeyboardSpringCurve : NSObject - (instancetype)initWithStiffness:(double)stiffness diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm index df78d85e33683..942dd18f2f8af 100644 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm @@ -6,12 +6,6 @@ #include -// This simplified spring model is based off of a damped harmonic oscillator. -// See: https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator -// -// This models the closed form of the second order differential equation which happens to match the -// algorithm used by CASpringAnimation, a QuartzCore (iOS) API that creates spring animations. -// // Spring code adapted from React Native's Animation Library, see: // https://github.com/facebook/react-native/blob/main/Libraries/Animated/animations/SpringAnimation.js @implementation FlutterKeyboardSpringCurve From b797c9189c8ead3e4db740bd97a2dd27c8341f21 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 2 Feb 2023 17:04:55 -0600 Subject: [PATCH 46/53] updated to work with third_party spring animation --- shell/platform/darwin/ios/BUILD.gn | 7 ++-- .../framework/Source/FlutterViewController.mm | 35 +++++++++------- .../Source/FlutterViewControllerTest.mm | 31 +++++++------- .../ios/framework/Source/spring_curve_ios.h | 30 ------------- .../ios/framework/Source/spring_curve_ios.mm | 42 ------------------- 5 files changed, 39 insertions(+), 106 deletions(-) delete mode 100644 shell/platform/darwin/ios/framework/Source/spring_curve_ios.h delete mode 100644 shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 82f55c6346329..327004b1cfe24 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -69,7 +69,9 @@ source_set("flutter_framework_source") { cflags_objc = flutter_cflags_objc cflags_objcc = flutter_cflags_objcc - deps = [] + deps = [ + "//flutter/third_party/spring_animation", + ] sources = [ "framework/Source/FlutterAppDelegate.mm", @@ -131,8 +133,6 @@ source_set("flutter_framework_source") { "framework/Source/platform_message_response_darwin.mm", "framework/Source/profiler_metrics_ios.h", "framework/Source/profiler_metrics_ios.mm", - "framework/Source/spring_curve_ios.h", - "framework/Source/spring_curve_ios.mm", "framework/Source/vsync_waiter_ios.h", "framework/Source/vsync_waiter_ios.mm", "ios_context.h", @@ -303,6 +303,7 @@ shared_library("ios_test_flutter") { "//flutter/shell/platform/darwin/common:framework_shared", "//flutter/shell/platform/embedder:embedder_as_internal_library", "//flutter/shell/platform/embedder:embedder_test_utils", + "//flutter/third_party/spring_animation", "//flutter/third_party/tonic", "//flutter/third_party/txt", "//third_party/ocmock:ocmock_shared", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index fe9aecb0e8fa5..203ff11810e33 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -27,10 +27,10 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h" #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" #import "flutter/shell/platform/embedder/embedder.h" +#import "flutter/third_party/spring_animation/spring_animation.h" static constexpr int kMicrosecondsPerSecond = 1000 * 1000; static constexpr CGFloat kScrollViewContentSize = 2.0; @@ -127,7 +127,7 @@ @implementation FlutterViewController { // https://github.com/flutter/flutter/issues/35050 fml::scoped_nsobject _scrollView; fml::scoped_nsobject _keyboardAnimationView; - fml::scoped_nsobject _keyboardSpringCurve; + fml::scoped_nsobject _keyboardSpringAnimation; MouseState _mouseState; // Timestamp after which a scroll inertia cancel event should be inferred. NSTimeInterval _scrollInertiaEventStartline; @@ -599,8 +599,8 @@ - (UIView*)keyboardAnimationView { return _keyboardAnimationView.get(); } -- (FlutterKeyboardSpringCurve*)keyboardSpringCurve { - return _keyboardSpringCurve.get(); +- (SpringAnimation*)keyboardSpringAnimation { + return _keyboardSpringAnimation.get(); } - (UIScreen*)mainScreenIfViewLoaded { @@ -1519,7 +1519,7 @@ - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger) } - (void)startKeyBoardAnimation:(NSTimeInterval)duration { - // If current physical_view_inset_bottom == targetViewInsetBottom,do nothing. + // If current physical_view_inset_bottom == targetViewInsetBottom, do nothing. if (_viewportMetrics.physical_view_inset_bottom == self.targetViewInsetBottom) { return; } @@ -1558,7 +1558,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Setup keyboard animation interpolation. CAAnimation* keyboardAnimation = [[self keyboardAnimationView].layer animationForKey:@"position"]; - [self setupKeyboardAnimationCurveIfNeeded:keyboardAnimation]; + [self setupKeyboardSpringAnimationIfNeeded:keyboardAnimation]; } completion:^(BOOL finished) { if (_keyboardAnimationVSyncClient == currentVsyncClient) { @@ -1572,20 +1572,23 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { }]; } -- (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation { +- (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation { // If keyboard animation is null or not a spring animation, fallback to DisplayLink tracking. if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) { - _keyboardSpringCurve.reset(); + _keyboardSpringAnimation.reset(); return; } // Setup keyboard spring animation details for spring curve animation calculation. - CASpringAnimation* keyboardSpringAnimation = (CASpringAnimation*)keyboardAnimation; - _keyboardSpringCurve.reset([[FlutterKeyboardSpringCurve alloc] - initWithStiffness:keyboardSpringAnimation.stiffness - damping:keyboardSpringAnimation.damping - mass:keyboardSpringAnimation.mass - initialVelocity:keyboardSpringAnimation.initialVelocity]); + CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation; + _keyboardSpringAnimation.reset([[SpringAnimation alloc] + initWithStiffness:keyboardCASpringAnimation.stiffness + damping:keyboardCASpringAnimation.damping + mass:keyboardCASpringAnimation.mass + initialVelocity:keyboardCASpringAnimation.initialVelocity + fromValue: 0.0 //self.originalViewInsetBottom + toValue: 1.0 //self.targetViewInsetBottom + ]); } - (void)setupKeyboardAnimationVsyncClient { @@ -1605,7 +1608,7 @@ - (void)setupKeyboardAnimationVsyncClient { [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; } - if ([flutterViewController keyboardSpringCurve] == nil) { + if ([flutterViewController keyboardSpringAnimation] == nil) { if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) { flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = flutterViewController.get() @@ -1618,7 +1621,7 @@ - (void)setupKeyboardAnimationVsyncClient { fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - flutterViewController.get().keyboardAnimationStartTime; double keyboardAnimationStop = - [[flutterViewController keyboardSpringCurve] curveFunc:timeElapsed.ToSecondsF()]; + [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = start + (end - start) * keyboardAnimationStop; [flutterViewController updateViewportMetrics]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 4101111ed5f94..0d6c95fa05901 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -16,8 +16,9 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h" #import "flutter/shell/platform/embedder/embedder.h" +#import "flutter/third_party/spring_animation/spring_animation.h" + FLUTTER_ASSERT_ARC @@ -136,8 +137,8 @@ - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGR - (void)startKeyBoardAnimation:(NSTimeInterval)duration; - (void)setupKeyboardAnimationVsyncClient; - (UIView*)keyboardAnimationView; -- (FlutterKeyboardSpringCurve*)keyboardSpringCurve; -- (void)setupKeyboardAnimationCurveIfNeeded:(CAAnimation*)keyboardAnimation; +- (SpringAnimation*)keyboardSpringAnimation; +- (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation; - (void)ensureViewportMetricsIsCorrect; - (void)invalidateKeyboardAnimationVSyncClient; - (void)addInternalPlugins; @@ -208,7 +209,7 @@ - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationVsyncClient { OCMVerify([viewControllerMock setupKeyboardAnimationVsyncClient]); } -- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationCurveIfNeeded { +- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine @@ -221,10 +222,10 @@ - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationCurveIfNeeded CAAnimation* keyboardAnimation = [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"]; - OCMVerify([viewControllerMock setupKeyboardAnimationCurveIfNeeded:keyboardAnimation]); + OCMVerify([viewControllerMock setupKeyboardSpringAnimationIfNeeded:keyboardAnimation]); } -- (void)testSetupKeyboardAnimationCurveIfNeeded { +- (void)testSetupKeyboardSpringAnimationIfNeeded { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine @@ -235,9 +236,9 @@ - (void)testSetupKeyboardAnimationCurveIfNeeded { [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame]; // Null check. - [viewControllerMock setupKeyboardAnimationCurveIfNeeded:nil]; - FlutterKeyboardSpringCurve* keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; - XCTAssertTrue(keyboardSpringCurve == nil); + [viewControllerMock setupKeyboardSpringAnimationIfNeeded:nil]; + SpringAnimation* keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation]; + XCTAssertTrue(keyboardSpringAnimation == nil); // CAAnimation that is not a CASpringAnimation. CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation]; @@ -245,10 +246,10 @@ - (void)testSetupKeyboardAnimationCurveIfNeeded { nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0]; nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0]; nonSpringAnimation.keyPath = @"position"; - [viewControllerMock setupKeyboardAnimationCurveIfNeeded:nonSpringAnimation]; - keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; + [viewControllerMock setupKeyboardSpringAnimationIfNeeded:nonSpringAnimation]; + keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation]; - XCTAssertTrue(keyboardSpringCurve == nil); + XCTAssertTrue(keyboardSpringAnimation == nil); // CASpringAnimation. CASpringAnimation* springAnimation = [CASpringAnimation animation]; @@ -258,9 +259,9 @@ - (void)testSetupKeyboardAnimationCurveIfNeeded { springAnimation.keyPath = @"position"; springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; - [viewControllerMock setupKeyboardAnimationCurveIfNeeded:springAnimation]; - keyboardSpringCurve = [viewControllerMock keyboardSpringCurve]; - XCTAssertTrue(keyboardSpringCurve != nil); + [viewControllerMock setupKeyboardSpringAnimationIfNeeded:springAnimation]; + keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation]; + XCTAssertTrue(keyboardSpringAnimation != nil); } - (void)testKeyboardAnimationIsShowingAndCompounding { diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h deleted file mode 100644 index 225588cd31ca9..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h +++ /dev/null @@ -1,30 +0,0 @@ -// 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. - -#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ -#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ - -#include - -// This simplified spring model is based off of a damped harmonic oscillator. -// See: https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator -// -// This models the closed form of the second order differential equation which happens to match the -// algorithm used by CASpringAnimation, a QuartzCore (iOS) API that creates spring animations. -@interface FlutterKeyboardSpringCurve : NSObject - -- (instancetype)initWithStiffness:(double)stiffness - damping:(double)damping - mass:(double)mass - initialVelocity:(double)initialVelocity; - -- (double)curveFunc:(double)t; - -@property(nonatomic, assign) double initialVelocity; -@property(nonatomic, assign) double zeta; -@property(nonatomic, assign) double omega0; -@property(nonatomic, assign) double omega1; -@end - -#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_SPRING_CURVE_IOS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm b/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm deleted file mode 100644 index 942dd18f2f8af..0000000000000 --- a/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm +++ /dev/null @@ -1,42 +0,0 @@ -// 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/spring_curve_ios.h" - -#include - -// Spring code adapted from React Native's Animation Library, see: -// https://github.com/facebook/react-native/blob/main/Libraries/Animated/animations/SpringAnimation.js -@implementation FlutterKeyboardSpringCurve -- (instancetype)initWithStiffness:(double)stiffness - damping:(double)damping - mass:(double)mass - initialVelocity:(double)initialVelocity { - self = [super init]; - if (self) { - _initialVelocity = initialVelocity; - _zeta = damping / (2 * sqrt(stiffness * mass)); // damping ratio - _omega0 = sqrt(stiffness / mass); // undamped angular frequency of the oscillator - _omega1 = self.omega0 * sqrt(1.0 - (self.zeta * self.zeta)); // exponential decay - } - return self; -} - -- (double)curveFunc:(double)t { - double y; - if (self.zeta < 1) { - // Under damped - double envelope = exp(-self.zeta * self.omega0 * t); - y = 1 - envelope * ((self.initialVelocity + self.zeta * self.omega0) / self.omega1 * - sin(self.omega1 * t) + - cos(self.omega1 * t)); - } else { - // Critically damped spring - double envelope = exp(-self.omega0 * t); - y = 1 - envelope * (1 + (self.initialVelocity + self.omega0) * t); - } - - return y; -} -@end From f4556a8a2ebbaefd24814f65cedcc6bbfa94d5cc Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 2 Feb 2023 17:56:16 -0600 Subject: [PATCH 47/53] updated to use SpringAnimation to/from values --- shell/platform/darwin/ios/BUILD.gn | 4 +-- .../framework/Source/FlutterViewController.mm | 34 ++++++++++++------- .../Source/FlutterViewControllerTest.mm | 1 - 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 327004b1cfe24..2c17aa038f664 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -69,9 +69,7 @@ source_set("flutter_framework_source") { cflags_objc = flutter_cflags_objc cflags_objcc = flutter_cflags_objcc - deps = [ - "//flutter/third_party/spring_animation", - ] + deps = [ "//flutter/third_party/spring_animation" ] sources = [ "framework/Source/FlutterAppDelegate.mm", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 203ff11810e33..ce91038b1a92f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -66,6 +66,7 @@ @interface FlutterViewController () GetVsyncTargetTime() - flutterViewController.get().keyboardAnimationStartTime; - double keyboardAnimationStop = - [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - start + (end - start) * keyboardAnimationStop; + [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; [flutterViewController updateViewportMetrics]; } }; @@ -1647,6 +1652,9 @@ - (void)removeKeyboardAnimationView { if ([self keyboardAnimationView].superview != nil) { [[self keyboardAnimationView] removeFromSuperview]; } + if (_keyboardSpringAnimation != nil) { + _keyboardSpringAnimation.reset(); + } } - (void)ensureViewportMetricsIsCorrect { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 0d6c95fa05901..1461ca0640993 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -19,7 +19,6 @@ #import "flutter/shell/platform/embedder/embedder.h" #import "flutter/third_party/spring_animation/spring_animation.h" - FLUTTER_ASSERT_ARC @interface FlutterEngine () From b9d0d787fee36c1e1bc78b04d34227003f81e12a Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 2 Feb 2023 18:39:41 -0600 Subject: [PATCH 48/53] updated license (removed old files) --- ci/licenses_golden/licenses_flutter | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 12cf0adcaf448..4d518c29f3bae 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2595,8 +2595,6 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_mes ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/ios_context.h + ../../../flutter/LICENSE @@ -5086,8 +5084,6 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_messa FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.h -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/module.modulemap From f35a1e8cf098a40af35c6142f4ccbf5b94586355 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Thu, 2 Feb 2023 21:04:41 -0600 Subject: [PATCH 49/53] updates --- shell/platform/darwin/ios/BUILD.gn | 3 ++- .../darwin/ios/framework/Source/FlutterViewController.mm | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 2c17aa038f664..7a30a1fb16d03 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -69,7 +69,7 @@ source_set("flutter_framework_source") { cflags_objc = flutter_cflags_objc cflags_objcc = flutter_cflags_objcc - deps = [ "//flutter/third_party/spring_animation" ] + deps = [] sources = [ "framework/Source/FlutterAppDelegate.mm", @@ -187,6 +187,7 @@ source_set("flutter_framework_source") { "//flutter/shell/platform/darwin/common:framework_shared", "//flutter/shell/platform/embedder:embedder_as_internal_library", "//flutter/shell/profiling:profiling", + "//flutter/third_party/spring_animation", "//third_party/skia", ] diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ce91038b1a92f..00a49a64ea11f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1652,9 +1652,7 @@ - (void)removeKeyboardAnimationView { if ([self keyboardAnimationView].superview != nil) { [[self keyboardAnimationView] removeFromSuperview]; } - if (_keyboardSpringAnimation != nil) { - _keyboardSpringAnimation.reset(); - } + _keyboardSpringAnimation.reset(); } - (void)ensureViewportMetricsIsCorrect { From 4ed2380f997cf85efd6a1a7d0cf03864352f2293 Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Fri, 3 Feb 2023 00:34:56 -0600 Subject: [PATCH 50/53] allow updating springanimation position values --- .../framework/Source/FlutterViewController.mm | 20 ++++++------------ .../spring_animation/SpringAnimationTest.mm | 21 +++++++++++++++++++ .../spring_animation/spring_animation.h | 4 ++-- .../spring_animation/spring_animation.mm | 10 ++++----- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 00a49a64ea11f..5e47fbf2a2c4e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -66,7 +66,6 @@ @interface FlutterViewController () Date: Fri, 3 Feb 2023 00:51:12 -0600 Subject: [PATCH 51/53] updated test --- .../spring_animation/SpringAnimationTest.mm | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/third_party/spring_animation/SpringAnimationTest.mm b/third_party/spring_animation/SpringAnimationTest.mm index b495ee477e638..110594093d222 100644 --- a/third_party/spring_animation/SpringAnimationTest.mm +++ b/third_party/spring_animation/SpringAnimationTest.mm @@ -39,23 +39,27 @@ ASSERT_TRUE(fabs(toValue - animation.toValue) < accuracy); } -TEST(SpringAnimationTest, CanUpdatePositionValuesCorrectly) { +TEST(SpringAnimationTest, CanUpdatePositionValuesAndCalculateCorrectly) { SpringAnimation* animation = [[SpringAnimation alloc] initWithStiffness:1000 damping:500 mass:3 initialVelocity:0 fromValue:0 toValue:1000]; - ASSERT_TRUE(animation.stiffness == 1000); - ASSERT_TRUE(animation.damping == 500); - ASSERT_TRUE(animation.mass == 3); - ASSERT_TRUE(animation.initialVelocity == 0); - ASSERT_TRUE(animation.fromValue == 0); - ASSERT_TRUE(animation.toValue == 1000); + const double startTime = 0; + const double endTime = 0.6; + + const double startValue1 = [animation curveFunction:startTime]; + const double toValue1 = [animation curveFunction:endTime]; animation.fromValue = 10; animation.toValue = 800; ASSERT_TRUE(animation.fromValue == 10); ASSERT_TRUE(animation.toValue == 800); + + const double startValue2 = [animation curveFunction:startTime]; + const double toValue2 = [animation curveFunction:endTime]; + ASSERT_TRUE(startValue2 > startValue1); + ASSERT_TRUE(toValue2 < toValue1); } From 13faeecc44f185a27e54f9cd35cb40d3228a7fad Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Fri, 3 Feb 2023 00:53:47 -0600 Subject: [PATCH 52/53] update --- .../darwin/ios/framework/Source/FlutterViewController.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 5e47fbf2a2c4e..16c59be66b9ff 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1357,8 +1357,8 @@ - (void)handleKeyboardNotification:(NSNotification*)notification { if (!keyboardAnimationIsCompounding) { [self startKeyBoardAnimation:duration]; - } else { - _keyboardSpringAnimation.get().toValue = self.targetViewInsetBottom; + } else if ([self keyboardSpringAnimation]) { + [self keyboardSpringAnimation].toValue = self.targetViewInsetBottom. } } From 4682d80705ef863922c2ef66e1149ee956c0126b Mon Sep 17 00:00:00 2001 From: Jake Schafer Date: Fri, 3 Feb 2023 01:08:19 -0600 Subject: [PATCH 53/53] fixes --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- third_party/spring_animation/spring_animation.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 16c59be66b9ff..ced158a9eda07 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1358,7 +1358,7 @@ - (void)handleKeyboardNotification:(NSNotification*)notification { if (!keyboardAnimationIsCompounding) { [self startKeyBoardAnimation:duration]; } else if ([self keyboardSpringAnimation]) { - [self keyboardSpringAnimation].toValue = self.targetViewInsetBottom. + [self keyboardSpringAnimation].toValue = self.targetViewInsetBottom; } } diff --git a/third_party/spring_animation/spring_animation.mm b/third_party/spring_animation/spring_animation.mm index d0e397033abd8..60016293a9f5d 100644 --- a/third_party/spring_animation/spring_animation.mm +++ b/third_party/spring_animation/spring_animation.mm @@ -48,7 +48,7 @@ - (instancetype)initWithStiffness:(double)stiffness } - (double)curveFunction:(double)t { - double x0 = _toValue - _fromValue; + const double x0 = _toValue - _fromValue; double y; if (_zeta < 1) {