import 'package:flutter/material.dart';

class BouncingBallApp extends StatefulWidget {
  const BouncingBallApp({Key? key}) : super(key: key);

  @override
  BouncingBallAppState createState() => BouncingBallAppState();
}

class BouncingBallAppState extends State<BouncingBallApp> with SingleTickerProviderStateMixin {
  double ballDiameter = 50;
  double floor = 0;
  double ceiling = 1.0;
  double ballPositionX = 50;
  double ballPositionY = 300;
  double ballVelocityX = 0;
  double ballVelocityY = 0;
  double gravity = 9.8;
  double restitution = 0.8;
  double friction = 0.98;
  late AnimationController _animationController;
  bool isDragging = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(minutes: 1), // set the duration to one minute
    )..repeat();
    _animationController.addListener(() {
      _updateBall(_animationController.lastElapsedDuration!.inMilliseconds.toDouble());
    });
  }

  void _onPanStart(DragStartDetails details) {
    setState(() {
      isDragging = true;
    });
  }

  void _onPanUpdate(DragUpdateDetails details) {
    setState(() {
      ballVelocityX += details.delta.dx / 2;
      ballVelocityY += -details.delta.dy / 2;
    });
  }

  void _onPanEnd(DragEndDetails details) {
    setState(() {
      isDragging = false;
      ballVelocityX *= friction;
      ballVelocityY *= friction;
    });
  }

  void _updateBall(double timeStamp) {
    double delta = timeStamp / 1000;

    if (!isDragging) {
      setState(() {
        ballPositionX += ballVelocityX * delta;
        ballPositionY += ballVelocityY * delta;

        double currentFloor = MediaQuery.of(context).size.height - ballDiameter / 2;
        if (ballPositionY > currentFloor) {
          ballPositionY = currentFloor;
          ballVelocityY *= -restitution;
          ballVelocityX *= friction;
        } else if (ballPositionY < ceiling + ballDiameter / 2) {
          ballPositionY = ceiling + ballDiameter / 2;
          ballVelocityY *= -restitution;
          ballVelocityX *= friction;
        } else {
          ballVelocityY += gravity * delta;
        }

        if (ballPositionX < ballDiameter / 2) {
          ballPositionX = ballDiameter / 2;
          ballVelocityX *= -restitution;
        } else if (ballPositionX > MediaQuery.of(context).size.width - ballDiameter / 2) {
          ballPositionX = MediaQuery.of(context).size.width - ballDiameter / 2;
          ballVelocityX *= -restitution;
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    floor = MediaQuery.of(context).size.height - ballDiameter / 2;
    return GestureDetector(
      onPanStart: _onPanStart,
      onPanUpdate: _onPanUpdate,
      onPanEnd: _onPanEnd,
      child: Scaffold(
        body: Center(
          child: Stack(
            children: <Widget>[
              Positioned.fill(
                child: Container(
                  color: Colors.grey[300],
                  child: const SizedBox.expand(),
                ),
              ),
              Positioned(
                left: ballPositionX - ballDiameter / 2,
                bottom: ballPositionY - ballDiameter / 2,
                child: Container(
                  width: ballDiameter,
                  height: ballDiameter,
                  decoration: const BoxDecoration(
                    color: Colors.red,
                    shape: BoxShape.circle,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

void main() => runApp(MaterialApp(home: BouncingBallApp(key: UniqueKey())));

Hello! I have a Flutter app, which code i put above. It simulates the bouncing ball behaviour. Can you remove animation controller and add Timer to rerun _updateBall() method?