// Copyright 2014 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 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

class MockOnEndFunction {
  int called = 0;

  void handler() {
    called++;
  }
}

const Duration animationDuration = Duration(milliseconds:1000);
const Duration additionalDelay = Duration(milliseconds:1);

void main() {
  late MockOnEndFunction mockOnEndFunction;
  const Key switchKey = Key('switchKey');

  setUp(() {
    mockOnEndFunction = MockOnEndFunction();
  });

  testWidgets('BoxConstraintsTween control test', (WidgetTester tester) async {
    final BoxConstraintsTween tween = BoxConstraintsTween(
      begin: BoxConstraints.tight(const Size(20.0, 50.0)),
      end: BoxConstraints.tight(const Size(10.0, 30.0)),
    );
    final BoxConstraints result = tween.lerp(0.25);
    expect(result.minWidth, 17.5);
    expect(result.maxWidth, 17.5);
    expect(result.minHeight, 45.0);
    expect(result.maxHeight, 45.0);
  });

  testWidgets('DecorationTween control test', (WidgetTester tester) async {
    final DecorationTween tween = DecorationTween(
      begin: const BoxDecoration(color: Color(0xFF00FF00)),
      end: const BoxDecoration(color: Color(0xFFFFFF00)),
    );
    final BoxDecoration result = tween.lerp(0.25) as BoxDecoration;
    expect(result.color, const Color(0xFF3FFF00));
  });

  testWidgets('EdgeInsetsTween control test', (WidgetTester tester) async {
    final EdgeInsetsTween tween = EdgeInsetsTween(
      begin: const EdgeInsets.symmetric(vertical: 50.0),
      end: const EdgeInsets.only(top: 10.0, bottom: 30.0),
    );
    final EdgeInsets result = tween.lerp(0.25);
    expect(result.left, 0.0);
    expect(result.right, 0.0);
    expect(result.top, 40.0);
    expect(result.bottom, 45.0);
  });

  testWidgets('Matrix4Tween control test', (WidgetTester tester) async {
    final Matrix4Tween tween = Matrix4Tween(
      begin: Matrix4.translationValues(10.0, 20.0, 30.0),
      end: Matrix4.translationValues(14.0, 24.0, 34.0),
    );
    final Matrix4 result = tween.lerp(0.25);
    expect(result, equals(Matrix4.translationValues(11.0, 21.0, 31.0)));
  });

  testWidgets('AnimatedContainer onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedContainerWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedPadding onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedPaddingWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedAlign onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedAlignWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedPositioned onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedPositionedWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedPositionedDirectional onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedPositionedDirectionalWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

   testWidgets('AnimatedSlide onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedSlideWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);
    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedSlide transition test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        switchKey: switchKey,
        state: _TestAnimatedSlideWidgetState(),
      ),
    ));

    final RebuildCountingState<StatefulWidget> state = tester.widget<TestAnimatedWidget>(
      find.byType(TestAnimatedWidget)
    ).rebuildState!;
    final Finder switchFinder = find.byKey(switchKey);
    final SlideTransition slideWidget = tester.widget<SlideTransition>(
      find.ancestor(
        of: find.byType(Placeholder),
        matching: find.byType(SlideTransition),
      ).first,
    );

    expect(state.builds, equals(1));

    await tester.tap(switchFinder);
    expect(state.builds, equals(1));
    await tester.pump();
    expect(slideWidget.position.value, equals(Offset.zero));
    expect(state.builds, equals(2));

    await tester.pump(const Duration(milliseconds: 500));
    expect(slideWidget.position.value, equals(const Offset(0.5,0.5)));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(slideWidget.position.value, equals(const Offset(0.75,0.75)));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(slideWidget.position.value, equals(const Offset(1,1)));
    expect(state.builds, equals(2));
  });

  testWidgets('AnimatedScale onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedScaleWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);
    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedScale transition test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        switchKey: switchKey,
        state: _TestAnimatedScaleWidgetState(),
      ),
    ));

    final RebuildCountingState<StatefulWidget> state = tester.widget<TestAnimatedWidget>(
      find.byType(TestAnimatedWidget)
    ).rebuildState!;
    final Finder switchFinder = find.byKey(switchKey);
    final ScaleTransition scaleWidget = tester.widget<ScaleTransition>(
      find.ancestor(
        of: find.byType(Placeholder),
        matching: find.byType(ScaleTransition),
      ).first,
    );

    expect(state.builds, equals(1));

    await tester.tap(switchFinder);
    expect(state.builds, equals(1));
    await tester.pump();
    expect(scaleWidget.scale.value, equals(1.0));
    expect(state.builds, equals(2));

    await tester.pump(const Duration(milliseconds: 500));
    expect(scaleWidget.scale.value, equals(1.5));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(scaleWidget.scale.value, equals(1.75));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(scaleWidget.scale.value, equals(2.0));
    expect(state.builds, equals(2));
  });

  testWidgets('AnimatedRotation onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedRotationWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);
    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedRotation transition test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        switchKey: switchKey,
        state: _TestAnimatedRotationWidgetState(),
      ),
    ));

    final RebuildCountingState<StatefulWidget> state = tester.widget<TestAnimatedWidget>(
        find.byType(TestAnimatedWidget)
    ).rebuildState!;
    final Finder switchFinder = find.byKey(switchKey);
    final RotationTransition rotationWidget = tester.widget<RotationTransition>(
      find.ancestor(
        of: find.byType(Placeholder),
        matching: find.byType(RotationTransition),
      ).first,
    );

    expect(state.builds, equals(1));

    await tester.tap(switchFinder);
    expect(state.builds, equals(1));
    await tester.pump();
    expect(rotationWidget.turns.value, equals(0.0));
    expect(state.builds, equals(2));

    await tester.pump(const Duration(milliseconds: 500));
    expect(rotationWidget.turns.value, equals(0.75));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(rotationWidget.turns.value, equals(1.125));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(rotationWidget.turns.value, equals(1.5));
    expect(state.builds, equals(2));
  });

  testWidgets('AnimatedOpacity onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedOpacityWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);
    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedOpacity transition test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        switchKey: switchKey,
        state: _TestAnimatedOpacityWidgetState(),
      ),
    ));

    final RebuildCountingState<StatefulWidget> state = tester.widget<TestAnimatedWidget>(
        find.byType(TestAnimatedWidget)
    ).rebuildState!;
    final Finder switchFinder = find.byKey(switchKey);
    final FadeTransition opacityWidget = tester.widget<FadeTransition>(
      find.ancestor(
        of: find.byType(Placeholder),
        matching: find.byType(FadeTransition),
      ).first,
    );

    expect(state.builds, equals(1));

    await tester.tap(switchFinder);
    expect(state.builds, equals(1));
    await tester.pump();
    expect(opacityWidget.opacity.value, equals(0.0));
    expect(state.builds, equals(2));

    await tester.pump(const Duration(milliseconds: 500));
    expect(opacityWidget.opacity.value, equals(0.5));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(opacityWidget.opacity.value, equals(0.75));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(opacityWidget.opacity.value, equals(1.0));
    expect(state.builds, equals(2));
  });

  testWidgets('AnimatedFractionallySizedBox onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedFractionallySizedBoxWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);
    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('SliverAnimatedOpacity onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(TestAnimatedWidget(
      callback: mockOnEndFunction.handler,
      switchKey: switchKey,
      state: _TestSliverAnimatedOpacityWidgetState(),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('SliverAnimatedOpacity transition test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        switchKey: switchKey,
        state: _TestSliverAnimatedOpacityWidgetState(),
      ),
    ));

    final RebuildCountingState<StatefulWidget> state = tester.widget<TestAnimatedWidget>(
        find.byType(TestAnimatedWidget)
    ).rebuildState!;
    final Finder switchFinder = find.byKey(switchKey);
    final SliverFadeTransition opacityWidget = tester.widget<SliverFadeTransition>(
      find.ancestor(
        of: find.byType(Placeholder),
        matching: find.byType(SliverFadeTransition),
      ).first,
    );

    expect(state.builds, equals(1));

    await tester.tap(switchFinder);
    expect(state.builds, equals(1));
    await tester.pump();
    expect(opacityWidget.opacity.value, equals(0.0));
    expect(state.builds, equals(2));

    await tester.pump(const Duration(milliseconds: 500));
    expect(opacityWidget.opacity.value, equals(0.5));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(opacityWidget.opacity.value, equals(0.75));
    expect(state.builds, equals(2));
    await tester.pump(const Duration(milliseconds: 250));
    expect(opacityWidget.opacity.value, equals(1.0));
    expect(state.builds, equals(2));
  });

  testWidgets('AnimatedDefaultTextStyle onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedDefaultTextStyleWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedPhysicalModel onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedPhysicalModelWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('TweenAnimationBuilder onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestTweenAnimationBuilderWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('AnimatedTheme onEnd callback test', (WidgetTester tester) async {
    await tester.pumpWidget(wrap(
      child: TestAnimatedWidget(
        callback: mockOnEndFunction.handler,
        switchKey: switchKey,
        state: _TestAnimatedThemeWidgetState(),
      ),
    ));

    final Finder widgetFinder = find.byKey(switchKey);

    await tester.tap(widgetFinder);

    await tester.pump();
    expect(mockOnEndFunction.called, 0);
    await tester.pump(animationDuration);
    expect(mockOnEndFunction.called, 0);
    await tester.pump(additionalDelay);
    expect(mockOnEndFunction.called, 1);

    await tapTest2and3(tester, widgetFinder, mockOnEndFunction);
  });

  testWidgets('Ensure CurvedAnimations are disposed on widget change',
      (WidgetTester tester) async {
    final GlobalKey<ImplicitlyAnimatedWidgetState<AnimatedOpacity>> key =
        GlobalKey<ImplicitlyAnimatedWidgetState<AnimatedOpacity>>();
    final ValueNotifier<Curve> curve = ValueNotifier<Curve>(const Interval(0.0, 0.5));
    addTearDown(curve.dispose);
    await tester.pumpWidget(wrap(
      child: ValueListenableBuilder<Curve>(
        valueListenable: curve,
        builder: (_, Curve c, __) => AnimatedOpacity(
            key: key,
            opacity: 1.0,
            duration: const Duration(seconds: 1),
            curve: c,
            child: Container(color: Colors.green)),
      ),
    ));

    final ImplicitlyAnimatedWidgetState<AnimatedOpacity>? firstState = key.currentState;
    final Animation<double>? firstAnimation = firstState?.animation;
    if (firstAnimation == null) {
      fail('animation was null!');
    }

    final CurvedAnimation firstCurvedAnimation =
        firstAnimation as CurvedAnimation;

    expect(firstCurvedAnimation.isDisposed, isFalse);

    curve.value = const Interval(0.0, 0.6);
    await tester.pumpAndSettle();

    final ImplicitlyAnimatedWidgetState<AnimatedOpacity>? secondState = key.currentState;
    final Animation<double>? secondAnimation = secondState?.animation;
    if (secondAnimation == null) {
      fail('animation was null!');
    }

    final CurvedAnimation secondCurvedAnimation = secondAnimation as CurvedAnimation;

    expect(firstState, equals(secondState));
    expect(firstAnimation, isNot(equals(secondAnimation)));

    expect(firstCurvedAnimation.isDisposed, isTrue);
    expect(secondCurvedAnimation.isDisposed, isFalse);

    await tester.pumpWidget(
      wrap(
        child: const Offstage(),
      ),
    );
    await tester.pumpAndSettle();

    expect(secondCurvedAnimation.isDisposed, isTrue);
  });
}

Future<void> tapTest2and3(WidgetTester tester, Finder widgetFinder,
    MockOnEndFunction mockOnEndFunction) async {
  await tester.tap(widgetFinder);

  await tester.pump();
  await tester.pump(animationDuration + additionalDelay);
  expect(mockOnEndFunction.called, 2);

  await tester.tap(widgetFinder);

  await tester.pump();
  await tester.pump(animationDuration + additionalDelay);
  expect(mockOnEndFunction.called, 3);
}

Widget wrap({required Widget child}) {
  return Directionality(
    textDirection: TextDirection.ltr,
    child: Material(
      child: Center(child: child),
    ),
  );
}

abstract class RebuildCountingState<T extends StatefulWidget> extends State<T> {
  int builds = 0;
}

class TestAnimatedWidget extends StatefulWidget {
  const TestAnimatedWidget({
    super.key,
    this.callback,
    required this.switchKey,
    required this.state,
  });
  final VoidCallback? callback;
  final Key switchKey;
  final State<StatefulWidget> state;

  RebuildCountingState<StatefulWidget>? get rebuildState =>
    state is RebuildCountingState<StatefulWidget> ? state as RebuildCountingState<StatefulWidget> : null;

  @override
  State<StatefulWidget> createState() => state; // ignore: no_logic_in_create_state, this test predates the lint
}

abstract class _TestAnimatedWidgetState extends RebuildCountingState<TestAnimatedWidget> {
  bool toggle = false;
  final Widget child = const Placeholder();
  final Duration duration = animationDuration;

  void onChanged(bool v) {
    setState(() {
      toggle = v;
    });
  }

  Widget getAnimatedWidget();

  @override
  Widget build(BuildContext context) {
    builds++;
    final Widget animatedWidget = getAnimatedWidget();

    return Stack(
      children: <Widget>[
        animatedWidget,
        Switch(key: widget.switchKey, value: toggle, onChanged: onChanged),
      ],
    );
  }
}

class _TestAnimatedContainerWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedContainer(
      duration: duration,
      onEnd: widget.callback,
      width: toggle ? 10 : 20,
      foregroundDecoration: toggle ? const BoxDecoration() : null,
      child: child,
    );
  }
}

class _TestAnimatedPaddingWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedPadding(
      duration: duration,
      onEnd: widget.callback,
      padding:
      toggle ? const EdgeInsets.all(8.0) : const EdgeInsets.all(16.0),
      child: child,
    );
  }
}

class _TestAnimatedAlignWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedAlign(
      duration: duration,
      onEnd: widget.callback,
      alignment: toggle ? Alignment.topLeft : Alignment.bottomRight,
      child: child,
    );
  }
}

class _TestAnimatedPositionedWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedPositioned(
      duration: duration,
      onEnd: widget.callback,
      left: toggle ? 10 : 20,
      child: child,
    );
  }
}

class _TestAnimatedPositionedDirectionalWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedPositionedDirectional(
      duration: duration,
      onEnd: widget.callback,
      start: toggle ? 10 : 20,
      child: child,
    );
  }
}

class _TestAnimatedSlideWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedSlide(
      duration: duration,
      onEnd: widget.callback,
      offset: toggle ? const Offset(1,1) : Offset.zero,
      child: child,
    );
  }
}

class _TestAnimatedScaleWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedScale(
      duration: duration,
      onEnd: widget.callback,
      scale: toggle ? 2.0 : 1.0,
      child: child,
    );
  }
}

class _TestAnimatedRotationWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedRotation(
      duration: duration,
      onEnd: widget.callback,
      turns: toggle ? 1.5 : 0.0,
      child: child,
    );
  }
}

class _TestAnimatedOpacityWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedOpacity(
      duration: duration,
      onEnd: widget.callback,
      opacity: toggle ? 1.0 : 0.0,
      child: child,
    );
  }
}

class _TestAnimatedFractionallySizedBoxWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedFractionallySizedBox(
      duration: duration,
      onEnd: widget.callback,
      heightFactor: toggle ? 0.25 : 0.75,
      widthFactor: toggle ? 0.25 : 0.75,
      child: child,
    );
  }
}

class _TestSliverAnimatedOpacityWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return SliverAnimatedOpacity(
      sliver: SliverToBoxAdapter(child: child),
      duration: duration,
      onEnd: widget.callback,
      opacity: toggle ? 1.0 : 0.0,
    );
  }

  @override
  Widget build(BuildContext context) {
    builds++;
    final Widget animatedWidget = getAnimatedWidget();

    return Material(
      child: Directionality(
        textDirection: TextDirection.ltr,
        child: CustomScrollView(
          slivers: <Widget>[
            animatedWidget,
            SliverToBoxAdapter(
              child: Switch(
                key: widget.switchKey,
                value: toggle,
                onChanged: onChanged,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _TestAnimatedDefaultTextStyleWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedDefaultTextStyle(
      duration: duration,
      onEnd: widget.callback,
      style: toggle
        ? const TextStyle(fontStyle: FontStyle.italic)
        : const TextStyle(fontStyle: FontStyle.normal),
      child: child,
    );
  }
}

class _TestAnimatedPhysicalModelWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedPhysicalModel(
      duration: duration,
      onEnd: widget.callback,
      color: toggle ? Colors.red : Colors.green,
      elevation: 0,
      shadowColor: Colors.blue,
      shape: BoxShape.rectangle,
      child: child,
    );
  }
}

class _TestTweenAnimationBuilderWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return TweenAnimationBuilder<double>(
      tween: toggle ? Tween<double>(begin: 1, end: 2) : Tween<double>(begin: 2, end: 1),
      duration: duration,
      onEnd: widget.callback,
      child: child,
      builder: (BuildContext context, double? size, Widget? child) {
        return SizedBox(
          width: size,
          height: size,
          child: child,
        );
      },
    );
  }
}

class _TestAnimatedThemeWidgetState extends _TestAnimatedWidgetState {
  @override
  Widget getAnimatedWidget() {
    return AnimatedTheme(
      data: toggle ? ThemeData.dark() : ThemeData.light(),
      duration: duration,
      onEnd: widget.callback,
      child: child,
    );
  }
}