Repository: fujidaiti/smooth_sheets
Pull request #240 (2024-09-04 14:23:58)

Refine animated activity behavior on content size changes
## Summary (check all that apply)

- [x] Modified / added code
- [x] Modified / added tests
- [ ] Modified / added examples
- [x] Modified / added others (pubspec.yaml, workflows, etc...)
- [ ] Updated README
- [ ] Contains breaking changes
  - [ ] Created / updated migration guide
- [ ] Incremented version number
  - [ ] Updated CHANGELOG



Patch:
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 0000000..75d8d60
--- /dev/null
+++ b/build.yaml
@@ -0,0 +1,6 @@
+targets:
+  $default:
+    builders:
+      mockito|mockBuilder:
+        generate_for:
+          - test/src/*.dart
diff --git a/lib/src/foundation/sheet_activity.dart b/lib/src/foundation/sheet_activity.dart
index 0428b6b..d070641 100644
--- a/lib/src/foundation/sheet_activity.dart
+++ b/lib/src/foundation/sheet_activity.dart
@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'dart:math';
+import 'dart:ui';
 
 import 'package:flutter/widgets.dart';
 import 'package:meta/meta.dart';
@@ -136,6 +137,18 @@ abstract class SheetActivity<T extends SheetExtent> {
   }
 }
 
+/// An activity that animates the [SheetExtent]'s `pixels` to a destination
+/// position resolved by [destination], using the specified [curve] and
+/// [duration].
+///
+/// This activity accepts the destination position as an [Extent], allowing
+/// the concrete end position (in pixels) to be adjusted during the animation
+/// in response to viewport changes, such as the appearance of the keyboard.
+///
+/// When the end position changes, this activity updates the [SheetExtent]'s
+/// `pixels` to maintain the sheet's visual position (referred to as *p*).
+/// In subsequent frames, the animated position is linearly interpolated
+/// between *p* and the new destination.
 @internal
 class AnimatedSheetActivity extends SheetActivity
     with ControlledSheetActivityMixin {
@@ -143,29 +156,48 @@ class AnimatedSheetActivity extends SheetActivity
     required this.destination,
     required this.duration,
     required this.curve,
-  }) : assert(duration > Duration.zero);
+  })  : _effectiveCurve = curve,
+        assert(duration > Duration.zero);
 
   final Extent destination;
   final Duration duration;
   final Curve curve;
 
+  late double _startPixels;
+  Curve _effectiveCurve;
+
+  @override
+  void init(SheetExtent delegate) {
+    super.init(delegate);
+    _startPixels = owner.metrics.pixels;
+  }
+
   @override
   AnimationController createAnimationController() {
-    return AnimationController.unbounded(
-      value: owner.metrics.pixels,
-      vsync: owner.context.vsync,
-    );
+    return AnimationController.unbounded(vsync: owner.context.vsync);
   }
 
   @override
   TickerFuture onAnimationStart() {
     return controller.animateTo(
-      destination.resolve(owner.metrics.contentSize),
+      1.0,
       duration: duration,
       curve: curve,
     );
   }
 
+  @override
+  void onAnimationTick() {
+    // The baseline may change during the animation, so we need to
+    // interpolate the current pixels in absolute coordinates. This ensures
+    // visual consistency regardless of baseline changes.
+    final endPixels = destination.resolve(owner.metrics.contentSize);
+    final progress = _effectiveCurve.transform(controller.value);
+    owner
+      ..setPixels(lerpDouble(_startPixels, endPixels, progress)!)
+      ..didUpdateMetrics();
+  }
+
   @override
   void onAnimationEnd() {
     owner.goBallistic(0);
@@ -177,25 +209,30 @@ class AnimatedSheetActivity extends SheetActivity
     Size? oldViewportSize,
     EdgeInsets? oldViewportInsets,
   ) {
-    // 1. Appends the delta of the bottom inset (typically the keyboard height)
+    // Appends the delta of the bottom inset (typically the keyboard height)
     // to keep the visual sheet position unchanged.
     final newInsets = owner.metrics.viewportInsets;
     final oldInsets = oldViewportInsets ?? newInsets;
     final deltaInsetBottom = newInsets.bottom - oldInsets.bottom;
+    final newPixels = owner.metrics.pixels - deltaInsetBottom;
     owner
-      ..setPixels(owner.metrics.pixels - deltaInsetBottom)
+      ..setPixels(newPixels)
       ..didUpdateMetrics();
 
-    // 2. If the animation is still running, we start a new linear animation
-    // to bring the sheet position to the recalculated final position in the
-    // remaining duration. We use a linear curve here because starting a curved
-    // animation in the middle of another curved animation tends to look jerky.
-    final newDestination = destination.resolve(owner.metrics.contentSize);
-    final elapsedDuration = controller.lastElapsedDuration ?? duration;
-    if (newDestination != controller.upperBound && elapsedDuration < duration) {
-      final carriedDuration = duration - elapsedDuration;
-      owner.animateTo(destination,
-          duration: carriedDuration, curve: Curves.linear);
+    if (oldContentSize == null) {
+      return;
+    }
+
+    final oldEndPixels = destination.resolve(oldContentSize);
+    final newEndPixels = destination.resolve(owner.metrics.contentSize);
+    final progress = controller.value;
+    if (oldEndPixels != newEndPixels && progress < 1) {
+      // The gradient of the line passing through the point
+      // (t=progress, newPixels) and (t=1.0, newEndPixels).
+      final gradient = (newEndPixels - newPixels) / (1 - progress);
+      // The new start position is the intersection of that line with t=0.
+      _startPixels = newEndPixels - gradient;
+      _effectiveCurve = Curves.linear;
     }
   }
 }
@@ -208,6 +245,13 @@ class BallisticSheetActivity extends SheetActivity
   });
 
   final Simulation simulation;
+  late double _lastAnimatedValue;
+
+  @override
+  void init(SheetExtent delegate) {
+    super.init(delegate);
+    _lastAnimatedValue = controller.value;
+  }
 
   @override
   AnimationController createAnimationController() {
@@ -219,6 +263,17 @@ class BallisticSheetActivity extends SheetActivity
     return controller.animateWith(simulation);
   }
 
+  @override
+  void onAnimationTick() {
+    if (mounted) {
+      final oldPixels = owner.metrics.pixels;
+      owner
+        ..setPixels(oldPixels + controller.value - _lastAnimatedValue)
+        ..didUpdateMetrics();
+      _lastAnimatedValue = controller.value;
+    }
+  }
+
   @override
   void onAnimationEnd() {
     owner.goBallistic(0);
@@ -294,7 +349,6 @@ class DragSheetActivity extends SheetActivity
 @optionalTypeArgs
 mixin ControlledSheetActivityMixin<T extends SheetExtent> on SheetActivity<T> {
   late final AnimationController controller;
-  late double _lastAnimatedValue;
 
   final _completer = Completer<void>();
   Future<void> get done => _completer.future;
@@ -302,6 +356,7 @@ mixin ControlledSheetActivityMixin<T extends SheetExtent> on SheetActivity<T> {
   @factory
   AnimationController createAnimationController();
   TickerFuture onAnimationStart();
+  void onAnimationTick();
   void onAnimationEnd() {}
 
   @override
@@ -316,17 +371,6 @@ mixin ControlledSheetActivityMixin<T extends SheetExtent> on SheetActivity<T> {
     controller = createAnimationController()..addListener(onAnimationTick);
     // Won't trigger if we dispose 'animation' first.
     onAnimationStart().whenComplete(onAnimationEnd);
-    _lastAnimatedValue = controller.value;
-  }
-
-  void onAnimationTick() {
-    if (mounted) {
-      final oldPixels = owner.metrics.pixels;
-      owner
-        ..setPixels(oldPixels + controller.value - _lastAnimatedValue)
-        ..didUpdateMetrics();
-      _lastAnimatedValue = controller.value;
-    }
   }
 
   @override
diff --git a/pubspec.yaml b/pubspec.yaml
index 206b9ea..8a5454d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -21,6 +21,8 @@ dependencies:
   meta: ^1.9.1
 
 dev_dependencies:
+  build_runner: ^2.4.9
   flutter_test:
     sdk: flutter
+  mockito: ^5.4.4
   pedantic_mono: ^1.23.0


Test patch:
diff --git a/test/foundation/sheet_activity_test.dart b/test/foundation/sheet_activity_test.dart
new file mode 100644
index 0000000..f298e0f
--- /dev/null
+++ b/test/foundation/sheet_activity_test.dart
@@ -0,0 +1,148 @@
+import 'package:flutter/animation.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+import 'package:smooth_sheets/src/foundation/sheet_activity.dart';
+import 'package:smooth_sheets/src/foundation/sheet_extent.dart';
+
+import '../src/stubbing.dart';
+
+class _TestAnimatedSheetActivity extends AnimatedSheetActivity {
+  _TestAnimatedSheetActivity({
+    required AnimationController controller,
+    required super.destination,
+    required super.duration,
+    required super.curve,
+  }) : _controller = controller;
+
+  final AnimationController _controller;
+
+  @override
+  AnimationController createAnimationController() {
+    return _controller;
+  }
+}
+
+void main() {
+  group('AnimatedSheetActivity', () {
+    late MockSheetExtent owner;
+    late MockAnimationController controller;
+
+    setUp(() {
+      owner = MockSheetExtent();
+      controller = MockAnimationController();
+
+      when(
+        controller.animateTo(
+          any,
+          duration: anyNamed('duration'),
+          curve: anyNamed('curve'),
+        ),
+      ).thenAnswer((_) => MockTickerFuture());
+    });
+
+    test('should animate to the destination', () {
+      when(owner.metrics).thenReturn(
+        const SheetMetrics(
+          pixels: 300,
+          minPixels: 300,
+          maxPixels: 700,
+          contentSize: Size(400, 700),
+          viewportSize: Size(400, 900),
+          viewportInsets: EdgeInsets.zero,
+        ),
+      );
+
+      final activity = _TestAnimatedSheetActivity(
+        controller: controller,
+        destination: const Extent.pixels(700),
+        duration: const Duration(milliseconds: 300),
+        curve: Curves.linear,
+      )..init(owner);
+
+      verify(
+        controller.animateTo(
+          1,
+          duration: const Duration(milliseconds: 300),
+          curve: Curves.linear,
+        ),
+      );
+
+      when(controller.value).thenReturn(0.0);
+      activity.onAnimationTick();
+      verify(owner.setPixels(300));
+
+      when(controller.value).thenReturn(0.5);
+      activity.onAnimationTick();
+      verify(owner.setPixels(500));
+
+      when(controller.value).thenReturn(1.0);
+      activity.onAnimationTick();
+      verify(owner.setPixels(700));
+    });
+
+    test('should absorb viewport changes', () {
+      var metrics = const SheetMetrics(
+        pixels: 300,
+        minPixels: 300,
+        maxPixels: 900,
+        contentSize: Size(400, 900),
+        viewportSize: Size(400, 900),
+        viewportInsets: EdgeInsets.zero,
+      );
+      when(owner.metrics).thenAnswer((_) => metrics);
+      when(owner.setPixels(any)).thenAnswer((invocation) {
+        final pixels = invocation.positionalArguments[0] as double;
+        metrics = metrics.copyWith(pixels: pixels);
+      });
+
+      final activity = _TestAnimatedSheetActivity(
+        controller: controller,
+        destination: const Extent.proportional(1),
+        duration: const Duration(milliseconds: 300),
+        curve: Curves.linear,
+      )..init(owner);
+
+      when(controller.value).thenReturn(0.0);
+      activity.onAnimationTick();
+      verify(owner.setPixels(300));
+
+      when(controller.value).thenReturn(0.25);
+      activity.onAnimationTick();
+      verify(owner.setPixels(450));
+
+      // The following lines simulate a viewport change, in which:
+      // 1. The viewport's bottom inset increases, simulating the
+      //    appearance of an on-screen keyboard.
+      // 2. The content size then reduces to avoid overlapping with the
+      //    increased bottom inset.
+      // This scenario mimics the behavior when opening a keyboard
+      // on a sheet that uses SheetContentScaffold.
+      final oldViewportInsets = metrics.viewportInsets;
+      final oldContentSize = metrics.contentSize;
+      metrics = metrics.copyWith(
+        maxPixels: 850,
+        viewportInsets: const EdgeInsets.only(bottom: 50),
+        contentSize: const Size(400, 850),
+      );
+      activity.didChangeViewportDimensions(null, oldViewportInsets);
+      activity.didChangeContentSize(oldContentSize);
+      activity.didFinalizeDimensions(oldContentSize, null, oldViewportInsets);
+      verify(owner.setPixels(400));
+      expect(metrics.viewPixels, 450,
+          reason: 'Visual position should not change when viewport changes.');
+
+      when(controller.value).thenReturn(0.5);
+      activity.onAnimationTick();
+      verify(owner.setPixels(550));
+
+      when(controller.value).thenReturn(0.75);
+      activity.onAnimationTick();
+      verify(owner.setPixels(700));
+
+      when(controller.value).thenReturn(1.0);
+      activity.onAnimationTick();
+      verify(owner.setPixels(850));
+    });
+  });
+}
diff --git a/test/src/stubbing.dart b/test/src/stubbing.dart
new file mode 100644
index 0000000..22d072d
--- /dev/null
+++ b/test/src/stubbing.dart
@@ -0,0 +1,10 @@
+import 'package:flutter/animation.dart';
+import 'package:mockito/annotations.dart';
+import 'package:smooth_sheets/src/foundation/sheet_extent.dart';
+
+@GenerateNiceMocks([
+  MockSpec<SheetExtent>(),
+  MockSpec<AnimationController>(),
+  MockSpec<TickerFuture>(),
+])
+export 'stubbing.mocks.dart';
diff --git a/test/src/stubbing.mocks.dart b/test/src/stubbing.mocks.dart
new file mode 100644
index 0000000..4067ee8
--- /dev/null
+++ b/test/src/stubbing.mocks.dart
@@ -0,0 +1,1193 @@
+// Mocks generated by Mockito 5.4.4 from annotations
+// in smooth_sheets/test/src/stubbing.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i8;
+import 'dart:ui' as _i12;
+
+import 'package:flutter/cupertino.dart' as _i7;
+import 'package:flutter/gestures.dart' as _i6;
+import 'package:flutter/src/animation/curves.dart' as _i13;
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:mockito/src/dummies.dart' as _i14;
+import 'package:smooth_sheets/src/foundation/sheet_activity.dart' as _i5;
+import 'package:smooth_sheets/src/foundation/sheet_context.dart' as _i2;
+import 'package:smooth_sheets/src/foundation/sheet_drag.dart' as _i9;
+import 'package:smooth_sheets/src/foundation/sheet_extent.dart' as _i3;
+import 'package:smooth_sheets/src/foundation/sheet_gesture_tamperer.dart'
+    as _i11;
+import 'package:smooth_sheets/src/foundation/sheet_physics.dart' as _i4;
+import 'package:smooth_sheets/src/foundation/sheet_status.dart' as _i10;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeSheetContext_0 extends _i1.SmartFake implements _i2.SheetContext {
+  _FakeSheetContext_0(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeSheetMetrics_1 extends _i1.SmartFake implements _i3.SheetMetrics {
+  _FakeSheetMetrics_1(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeExtent_2 extends _i1.SmartFake implements _i3.Extent {
+  _FakeExtent_2(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeSheetPhysics_3 extends _i1.SmartFake implements _i4.SheetPhysics {
+  _FakeSheetPhysics_3(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeSheetActivity_4<T extends _i3.SheetExtent> extends _i1.SmartFake
+    implements _i5.SheetActivity<T> {
+  _FakeSheetActivity_4(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeDrag_5 extends _i1.SmartFake implements _i6.Drag {
+  _FakeDrag_5(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeAnimation_6<T> extends _i1.SmartFake implements _i7.Animation<T> {
+  _FakeAnimation_6(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeTickerFuture_7 extends _i1.SmartFake implements _i7.TickerFuture {
+  _FakeTickerFuture_7(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+class _FakeFuture_8<T> extends _i1.SmartFake implements _i8.Future<T> {
+  _FakeFuture_8(
+    Object parent,
+    Invocation parentInvocation,
+  ) : super(
+          parent,
+          parentInvocation,
+        );
+}
+
+/// A class which mocks [SheetExtent].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockSheetExtent extends _i1.Mock implements _i3.SheetExtent {
+  @override
+  _i2.SheetContext get context => (super.noSuchMethod(
+        Invocation.getter(#context),
+        returnValue: _FakeSheetContext_0(
+          this,
+          Invocation.getter(#context),
+        ),
+        returnValueForMissingStub: _FakeSheetContext_0(
+          this,
+          Invocation.getter(#context),
+        ),
+      ) as _i2.SheetContext);
+
+  @override
+  set currentDrag(_i9.SheetDragController? _currentDrag) => super.noSuchMethod(
+        Invocation.setter(
+          #currentDrag,
+          _currentDrag,
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  _i3.SheetMetrics get value => (super.noSuchMethod(
+        Invocation.getter(#value),
+        returnValue: _FakeSheetMetrics_1(
+          this,
+          Invocation.getter(#value),
+        ),
+        returnValueForMissingStub: _FakeSheetMetrics_1(
+          this,
+          Invocation.getter(#value),
+        ),
+      ) as _i3.SheetMetrics);
+
+  @override
+  _i10.SheetStatus get status => (super.noSuchMethod(
+        Invocation.getter(#status),
+        returnValue: _i10.SheetStatus.stable,
+        returnValueForMissingStub: _i10.SheetStatus.stable,
+      ) as _i10.SheetStatus);
+
+  @override
+  _i3.Extent get minExtent => (super.noSuchMethod(
+        Invocation.getter(#minExtent),
+        returnValue: _FakeExtent_2(
+          this,
+          Invocation.getter(#minExtent),
+        ),
+        returnValueForMissingStub: _FakeExtent_2(
+          this,
+          Invocation.getter(#minExtent),
+        ),
+      ) as _i3.Extent);
+
+  @override
+  _i3.Extent get maxExtent => (super.noSuchMethod(
+        Invocation.getter(#maxExtent),
+        returnValue: _FakeExtent_2(
+          this,
+          Invocation.getter(#maxExtent),
+        ),
+        returnValueForMissingStub: _FakeExtent_2(
+          this,
+          Invocation.getter(#maxExtent),
+        ),
+      ) as _i3.Extent);
+
+  @override
+  _i4.SheetPhysics get physics => (super.noSuchMethod(
+        Invocation.getter(#physics),
+        returnValue: _FakeSheetPhysics_3(
+          this,
+          Invocation.getter(#physics),
+        ),
+        returnValueForMissingStub: _FakeSheetPhysics_3(
+          this,
+          Invocation.getter(#physics),
+        ),
+      ) as _i4.SheetPhysics);
+
+  @override
+  _i5.SheetActivity<_i3.SheetExtent> get activity => (super.noSuchMethod(
+        Invocation.getter(#activity),
+        returnValue: _FakeSheetActivity_4<_i3.SheetExtent>(
+          this,
+          Invocation.getter(#activity),
+        ),
+        returnValueForMissingStub: _FakeSheetActivity_4<_i3.SheetExtent>(
+          this,
+          Invocation.getter(#activity),
+        ),
+      ) as _i5.SheetActivity<_i3.SheetExtent>);
+
+  @override
+  _i3.SheetMetrics get metrics => (super.noSuchMethod(
+        Invocation.getter(#metrics),
+        returnValue: _FakeSheetMetrics_1(
+          this,
+          Invocation.getter(#metrics),
+        ),
+        returnValueForMissingStub: _FakeSheetMetrics_1(
+          this,
+          Invocation.getter(#metrics),
+        ),
+      ) as _i3.SheetMetrics);
+
+  @override
+  bool get hasListeners => (super.noSuchMethod(
+        Invocation.getter(#hasListeners),
+        returnValue: false,
+        returnValueForMissingStub: false,
+      ) as bool);
+
+  @override
+  void takeOver(_i3.SheetExtent? other) => super.noSuchMethod(
+        Invocation.method(
+          #takeOver,
+          [other],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void updateGestureTamperer(_i11.SheetGestureTamperer? gestureTamperer) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #updateGestureTamperer,
+          [gestureTamperer],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void updatePhysics(_i4.SheetPhysics? physics) => super.noSuchMethod(
+        Invocation.method(
+          #updatePhysics,
+          [physics],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void applyNewContentSize(_i12.Size? contentSize) => super.noSuchMethod(
+        Invocation.method(
+          #applyNewContentSize,
+          [contentSize],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void applyNewViewportDimensions(
+    _i12.Size? size,
+    _i7.EdgeInsets? insets,
+  ) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #applyNewViewportDimensions,
+          [
+            size,
+            insets,
+          ],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void applyNewBoundaryConstraints(
+    _i3.Extent? minExtent,
+    _i3.Extent? maxExtent,
+  ) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #applyNewBoundaryConstraints,
+          [
+            minExtent,
+            maxExtent,
+          ],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void markAsDimensionsWillChange() => super.noSuchMethod(
+        Invocation.method(
+          #markAsDimensionsWillChange,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void markAsDimensionsChanged() => super.noSuchMethod(
+        Invocation.method(
+          #markAsDimensionsChanged,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void onDimensionsFinalized() => super.noSuchMethod(
+        Invocation.method(
+          #onDimensionsFinalized,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void beginActivity(_i5.SheetActivity<_i3.SheetExtent>? activity) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #beginActivity,
+          [activity],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void goIdle() => super.noSuchMethod(
+        Invocation.method(
+          #goIdle,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void goBallistic(double? velocity) => super.noSuchMethod(
+        Invocation.method(
+          #goBallistic,
+          [velocity],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void goBallisticWith(_i7.Simulation? simulation) => super.noSuchMethod(
+        Invocation.method(
+          #goBallisticWith,
+          [simulation],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void settle() => super.noSuchMethod(
+        Invocation.method(
+          #settle,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  _i6.Drag drag(
+    _i7.DragStartDetails? details,
+    _i12.VoidCallback? dragCancelCallback,
+  ) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #drag,
+          [
+            details,
+            dragCancelCallback,
+          ],
+        ),
+        returnValue: _FakeDrag_5(
+          this,
+          Invocation.method(
+            #drag,
+            [
+              details,
+              dragCancelCallback,
+            ],
+          ),
+        ),
+        returnValueForMissingStub: _FakeDrag_5(
+          this,
+          Invocation.method(
+            #drag,
+            [
+              details,
+              dragCancelCallback,
+            ],
+          ),
+        ),
+      ) as _i6.Drag);
+
+  @override
+  void dispose() => super.noSuchMethod(
+        Invocation.method(
+          #dispose,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void setPixels(double? pixels) => super.noSuchMethod(
+        Invocation.method(
+          #setPixels,
+          [pixels],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void correctPixels(double? pixels) => super.noSuchMethod(
+        Invocation.method(
+          #correctPixels,
+          [pixels],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  _i8.Future<void> animateTo(
+    _i3.Extent? newExtent, {
+    _i7.Curve? curve = _i7.Curves.easeInOut,
+    Duration? duration = const Duration(milliseconds: 300),
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #animateTo,
+          [newExtent],
+          {
+            #curve: curve,
+            #duration: duration,
+          },
+        ),
+        returnValue: _i8.Future<void>.value(),
+        returnValueForMissingStub: _i8.Future<void>.value(),
+      ) as _i8.Future<void>);
+
+  @override
+  void didUpdateMetrics() => super.noSuchMethod(
+        Invocation.method(
+          #didUpdateMetrics,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void didDragStart(_i9.SheetDragStartDetails? details) => super.noSuchMethod(
+        Invocation.method(
+          #didDragStart,
+          [details],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void didDragEnd(_i9.SheetDragEndDetails? details) => super.noSuchMethod(
+        Invocation.method(
+          #didDragEnd,
+          [details],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void didDragUpdateMetrics(_i9.SheetDragUpdateDetails? details) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #didDragUpdateMetrics,
+          [details],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void didDragCancel() => super.noSuchMethod(
+        Invocation.method(
+          #didDragCancel,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void didOverflowBy(double? overflow) => super.noSuchMethod(
+        Invocation.method(
+          #didOverflowBy,
+          [overflow],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void addListener(_i12.VoidCallback? listener) => super.noSuchMethod(
+        Invocation.method(
+          #addListener,
+          [listener],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod(
+        Invocation.method(
+          #removeListener,
+          [listener],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void notifyListeners() => super.noSuchMethod(
+        Invocation.method(
+          #notifyListeners,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+}
+
+/// A class which mocks [AnimationController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockAnimationController extends _i1.Mock
+    implements _i7.AnimationController {
+  @override
+  double get lowerBound => (super.noSuchMethod(
+        Invocation.getter(#lowerBound),
+        returnValue: 0.0,
+        returnValueForMissingStub: 0.0,
+      ) as double);
+
+  @override
+  double get upperBound => (super.noSuchMethod(
+        Invocation.getter(#upperBound),
+        returnValue: 0.0,
+        returnValueForMissingStub: 0.0,
+      ) as double);
+
+  @override
+  _i7.AnimationBehavior get animationBehavior => (super.noSuchMethod(
+        Invocation.getter(#animationBehavior),
+        returnValue: _i7.AnimationBehavior.normal,
+        returnValueForMissingStub: _i7.AnimationBehavior.normal,
+      ) as _i7.AnimationBehavior);
+
+  @override
+  set duration(Duration? _duration) => super.noSuchMethod(
+        Invocation.setter(
+          #duration,
+          _duration,
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  set reverseDuration(Duration? _reverseDuration) => super.noSuchMethod(
+        Invocation.setter(
+          #reverseDuration,
+          _reverseDuration,
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  _i7.Animation<double> get view => (super.noSuchMethod(
+        Invocation.getter(#view),
+        returnValue: _FakeAnimation_6<double>(
+          this,
+          Invocation.getter(#view),
+        ),
+        returnValueForMissingStub: _FakeAnimation_6<double>(
+          this,
+          Invocation.getter(#view),
+        ),
+      ) as _i7.Animation<double>);
+
+  @override
+  double get value => (super.noSuchMethod(
+        Invocation.getter(#value),
+        returnValue: 0.0,
+        returnValueForMissingStub: 0.0,
+      ) as double);
+
+  @override
+  set value(double? newValue) => super.noSuchMethod(
+        Invocation.setter(
+          #value,
+          newValue,
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  double get velocity => (super.noSuchMethod(
+        Invocation.getter(#velocity),
+        returnValue: 0.0,
+        returnValueForMissingStub: 0.0,
+      ) as double);
+
+  @override
+  bool get isAnimating => (super.noSuchMethod(
+        Invocation.getter(#isAnimating),
+        returnValue: false,
+        returnValueForMissingStub: false,
+      ) as bool);
+
+  @override
+  _i7.AnimationStatus get status => (super.noSuchMethod(
+        Invocation.getter(#status),
+        returnValue: _i7.AnimationStatus.dismissed,
+        returnValueForMissingStub: _i7.AnimationStatus.dismissed,
+      ) as _i7.AnimationStatus);
+
+  @override
+  bool get isDismissed => (super.noSuchMethod(
+        Invocation.getter(#isDismissed),
+        returnValue: false,
+        returnValueForMissingStub: false,
+      ) as bool);
+
+  @override
+  bool get isCompleted => (super.noSuchMethod(
+        Invocation.getter(#isCompleted),
+        returnValue: false,
+        returnValueForMissingStub: false,
+      ) as bool);
+
+  @override
+  void resync(_i7.TickerProvider? vsync) => super.noSuchMethod(
+        Invocation.method(
+          #resync,
+          [vsync],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void reset() => super.noSuchMethod(
+        Invocation.method(
+          #reset,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  _i7.TickerFuture forward({double? from}) => (super.noSuchMethod(
+        Invocation.method(
+          #forward,
+          [],
+          {#from: from},
+        ),
+        returnValue: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #forward,
+            [],
+            {#from: from},
+          ),
+        ),
+        returnValueForMissingStub: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #forward,
+            [],
+            {#from: from},
+          ),
+        ),
+      ) as _i7.TickerFuture);
+
+  @override
+  _i7.TickerFuture reverse({double? from}) => (super.noSuchMethod(
+        Invocation.method(
+          #reverse,
+          [],
+          {#from: from},
+        ),
+        returnValue: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #reverse,
+            [],
+            {#from: from},
+          ),
+        ),
+        returnValueForMissingStub: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #reverse,
+            [],
+            {#from: from},
+          ),
+        ),
+      ) as _i7.TickerFuture);
+
+  @override
+  _i7.TickerFuture animateTo(
+    double? target, {
+    Duration? duration,
+    _i7.Curve? curve = _i13.Curves.linear,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #animateTo,
+          [target],
+          {
+            #duration: duration,
+            #curve: curve,
+          },
+        ),
+        returnValue: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #animateTo,
+            [target],
+            {
+              #duration: duration,
+              #curve: curve,
+            },
+          ),
+        ),
+        returnValueForMissingStub: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #animateTo,
+            [target],
+            {
+              #duration: duration,
+              #curve: curve,
+            },
+          ),
+        ),
+      ) as _i7.TickerFuture);
+
+  @override
+  _i7.TickerFuture animateBack(
+    double? target, {
+    Duration? duration,
+    _i7.Curve? curve = _i13.Curves.linear,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #animateBack,
+          [target],
+          {
+            #duration: duration,
+            #curve: curve,
+          },
+        ),
+        returnValue: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #animateBack,
+            [target],
+            {
+              #duration: duration,
+              #curve: curve,
+            },
+          ),
+        ),
+        returnValueForMissingStub: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #animateBack,
+            [target],
+            {
+              #duration: duration,
+              #curve: curve,
+            },
+          ),
+        ),
+      ) as _i7.TickerFuture);
+
+  @override
+  _i7.TickerFuture repeat({
+    double? min,
+    double? max,
+    bool? reverse = false,
+    Duration? period,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #repeat,
+          [],
+          {
+            #min: min,
+            #max: max,
+            #reverse: reverse,
+            #period: period,
+          },
+        ),
+        returnValue: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #repeat,
+            [],
+            {
+              #min: min,
+              #max: max,
+              #reverse: reverse,
+              #period: period,
+            },
+          ),
+        ),
+        returnValueForMissingStub: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #repeat,
+            [],
+            {
+              #min: min,
+              #max: max,
+              #reverse: reverse,
+              #period: period,
+            },
+          ),
+        ),
+      ) as _i7.TickerFuture);
+
+  @override
+  _i7.TickerFuture fling({
+    double? velocity = 1.0,
+    _i7.SpringDescription? springDescription,
+    _i7.AnimationBehavior? animationBehavior,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #fling,
+          [],
+          {
+            #velocity: velocity,
+            #springDescription: springDescription,
+            #animationBehavior: animationBehavior,
+          },
+        ),
+        returnValue: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #fling,
+            [],
+            {
+              #velocity: velocity,
+              #springDescription: springDescription,
+              #animationBehavior: animationBehavior,
+            },
+          ),
+        ),
+        returnValueForMissingStub: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #fling,
+            [],
+            {
+              #velocity: velocity,
+              #springDescription: springDescription,
+              #animationBehavior: animationBehavior,
+            },
+          ),
+        ),
+      ) as _i7.TickerFuture);
+
+  @override
+  _i7.TickerFuture animateWith(_i7.Simulation? simulation) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #animateWith,
+          [simulation],
+        ),
+        returnValue: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #animateWith,
+            [simulation],
+          ),
+        ),
+        returnValueForMissingStub: _FakeTickerFuture_7(
+          this,
+          Invocation.method(
+            #animateWith,
+            [simulation],
+          ),
+        ),
+      ) as _i7.TickerFuture);
+
+  @override
+  void stop({bool? canceled = true}) => super.noSuchMethod(
+        Invocation.method(
+          #stop,
+          [],
+          {#canceled: canceled},
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void dispose() => super.noSuchMethod(
+        Invocation.method(
+          #dispose,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  String toStringDetails() => (super.noSuchMethod(
+        Invocation.method(
+          #toStringDetails,
+          [],
+        ),
+        returnValue: _i14.dummyValue<String>(
+          this,
+          Invocation.method(
+            #toStringDetails,
+            [],
+          ),
+        ),
+        returnValueForMissingStub: _i14.dummyValue<String>(
+          this,
+          Invocation.method(
+            #toStringDetails,
+            [],
+          ),
+        ),
+      ) as String);
+
+  @override
+  void addListener(_i12.VoidCallback? listener) => super.noSuchMethod(
+        Invocation.method(
+          #addListener,
+          [listener],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod(
+        Invocation.method(
+          #removeListener,
+          [listener],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void addStatusListener(_i7.AnimationStatusListener? listener) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #addStatusListener,
+          [listener],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void removeStatusListener(_i7.AnimationStatusListener? listener) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #removeStatusListener,
+          [listener],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  _i7.Animation<U> drive<U>(_i7.Animatable<U>? child) => (super.noSuchMethod(
+        Invocation.method(
+          #drive,
+          [child],
+        ),
+        returnValue: _FakeAnimation_6<U>(
+          this,
+          Invocation.method(
+            #drive,
+            [child],
+          ),
+        ),
+        returnValueForMissingStub: _FakeAnimation_6<U>(
+          this,
+          Invocation.method(
+            #drive,
+            [child],
+          ),
+        ),
+      ) as _i7.Animation<U>);
+
+  @override
+  void didRegisterListener() => super.noSuchMethod(
+        Invocation.method(
+          #didRegisterListener,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void didUnregisterListener() => super.noSuchMethod(
+        Invocation.method(
+          #didUnregisterListener,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void clearListeners() => super.noSuchMethod(
+        Invocation.method(
+          #clearListeners,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void notifyListeners() => super.noSuchMethod(
+        Invocation.method(
+          #notifyListeners,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void clearStatusListeners() => super.noSuchMethod(
+        Invocation.method(
+          #clearStatusListeners,
+          [],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  void notifyStatusListeners(_i7.AnimationStatus? status) => super.noSuchMethod(
+        Invocation.method(
+          #notifyStatusListeners,
+          [status],
+        ),
+        returnValueForMissingStub: null,
+      );
+}
+
+/// A class which mocks [TickerFuture].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockTickerFuture extends _i1.Mock implements _i7.TickerFuture {
+  @override
+  _i8.Future<void> get orCancel => (super.noSuchMethod(
+        Invocation.getter(#orCancel),
+        returnValue: _i8.Future<void>.value(),
+        returnValueForMissingStub: _i8.Future<void>.value(),
+      ) as _i8.Future<void>);
+
+  @override
+  void whenCompleteOrCancel(_i12.VoidCallback? callback) => super.noSuchMethod(
+        Invocation.method(
+          #whenCompleteOrCancel,
+          [callback],
+        ),
+        returnValueForMissingStub: null,
+      );
+
+  @override
+  _i8.Stream<void> asStream() => (super.noSuchMethod(
+        Invocation.method(
+          #asStream,
+          [],
+        ),
+        returnValue: _i8.Stream<void>.empty(),
+        returnValueForMissingStub: _i8.Stream<void>.empty(),
+      ) as _i8.Stream<void>);
+
+  @override
+  _i8.Future<void> catchError(
+    Function? onError, {
+    bool Function(Object)? test,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #catchError,
+          [onError],
+          {#test: test},
+        ),
+        returnValue: _i8.Future<void>.value(),
+        returnValueForMissingStub: _i8.Future<void>.value(),
+      ) as _i8.Future<void>);
+
+  @override
+  _i8.Future<R> then<R>(
+    _i8.FutureOr<R> Function(void)? onValue, {
+    Function? onError,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #then,
+          [onValue],
+          {#onError: onError},
+        ),
+        returnValue: _i14.ifNotNull(
+              _i14.dummyValueOrNull<R>(
+                this,
+                Invocation.method(
+                  #then,
+                  [onValue],
+                  {#onError: onError},
+                ),
+              ),
+              (R v) => _i8.Future<R>.value(v),
+            ) ??
+            _FakeFuture_8<R>(
+              this,
+              Invocation.method(
+                #then,
+                [onValue],
+                {#onError: onError},
+              ),
+            ),
+        returnValueForMissingStub: _i14.ifNotNull(
+              _i14.dummyValueOrNull<R>(
+                this,
+                Invocation.method(
+                  #then,
+                  [onValue],
+                  {#onError: onError},
+                ),
+              ),
+              (R v) => _i8.Future<R>.value(v),
+            ) ??
+            _FakeFuture_8<R>(
+              this,
+              Invocation.method(
+                #then,
+                [onValue],
+                {#onError: onError},
+              ),
+            ),
+      ) as _i8.Future<R>);
+
+  @override
+  _i8.Future<void> timeout(
+    Duration? timeLimit, {
+    _i8.FutureOr<void> Function()? onTimeout,
+  }) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #timeout,
+          [timeLimit],
+          {#onTimeout: onTimeout},
+        ),
+        returnValue: _i8.Future<void>.value(),
+        returnValueForMissingStub: _i8.Future<void>.value(),
+      ) as _i8.Future<void>);
+
+  @override
+  _i8.Future<void> whenComplete(dynamic Function()? action) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #whenComplete,
+          [action],
+        ),
+        returnValue: _i8.Future<void>.value(),
+        returnValueForMissingStub: _i8.Future<void>.value(),
+      ) as _i8.Future<void>);
+}