import 'dart:async';

import 'package:basketball/constants.dart';
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> {
  double floor = 1.0;
  double ceiling = 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;
  bool isDragging = false;

  @override
  void initState() {
    super.initState();
    Timer.periodic(const Duration(milliseconds: 16), (timer) {
      _updateBall(16);
    });
  }

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

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

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

  void _updateBall(double delta) {
    if (!isDragging) {
      setState(() {
        ballPositionX += ballVelocityX * delta / 100;
        ballPositionY += ballVelocityY * delta / 100;

        double currentCeiling = 0 + AppConstants.ballSize / 2;
        if (ballPositionY < currentCeiling) {
          ballPositionY = currentCeiling;
          ballVelocityY *= -restitution;
          ballVelocityX *= restitution;
        } else if (ballPositionY > floor - AppConstants.ballSize / 2) {
          ballPositionY = floor - AppConstants.ballSize / 2;
          ballVelocityY *= -restitution;
          ballVelocityX *= restitution;
        } else {
          ballVelocityY += gravity * delta / 100;
        }

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

  @override
  Widget build(BuildContext context) {
    floor = MediaQuery.of(context).size.height;
    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 - AppConstants.ballSize / 2,
                  top: ballPositionY - AppConstants.ballSize / 2,
                  child: const Image(
                    width: AppConstants.ballSize,
                    height: AppConstants.ballSize,
                    image: AssetImage('assets/ball.png'),
                  )),
            ],
          ),
        ),
      ),
    );
  }
}

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

Hello! I have a Flutter app, that simulates the bouncing ball behavior (code of the app is mentioned above). Right now I have an issue, that ball is jumping endlessly on the bottom of screen, when its must to just stop. Can you fix it? Also, can you add a restriction, that not allow user to adjust ball speed if current ball speed is higher than 3?