library;

import 'package:flutter/material.dart';
import 'package:page_transition/page_transition.dart';

enum SplashType { simpleSplash, backgroundScreenReturn }

enum SplashTransition {
  slideTransition,
  scaleTransition,
  rotationTransition,

  sizeTransition,
  fadeTransition,
  decoratedBoxTransition,
}

class AnimatedSplashScreen extends StatefulWidget {
  /// Type of page transition
  final PageTransitionType transitionType;

  /// Type of splash transition
  final SplashTransition splashTransition;

  /// Only required while using [AnimatedSplashScreen.withScreenFunction]
  /// here you pass your function that needs to be called before jumping
  /// on to the next screen
  final Future Function()? function;

  /// Custom animation to icon of splash
  final Animatable? customAnimation;

  /// Background color
  final Color backgroundColor;

  /// Compulsory in default constructor.
  /// Here you pass your widget that will be browsed
  final Widget? nextScreen;

  /// Type of AnimatedSplashScreen
  final SplashType type;

  /// to make the icon in [Center] of splash
  final bool centered;

  /// If you want to implement the navigation to the next page yourself.
  /// By default this is [false], the widget will call Navigator.of(_context).pushReplacement()
  /// using PageTransition with [transitionType] after [duration] to [nextScreen]
  final bool disableNavigation;

  /// It can be string for [Image.asserts], normal [Widget] or you can user tags
  /// to choose which one you image type, for example:
  /// * '[n]www.my-site.com/my-image.png' to [Image.network]
  final dynamic splash;

  /// Time in milliseconds after splash animation to jump to next screen
  /// Default is [milliseconds: 2500], minimum is [milliseconds: 100]
  final int duration;

  /// Curve of splash animation
  final Curve curve;

  /// Splash animation duration, default is [milliseconds: 800]
  final Duration? animationDuration;

  /// Size of an icon in splash screen
  final double? splashIconSize;

  /// Here you pass your route that will be browsed
  final String? nextRoute;

  factory AnimatedSplashScreen({
    Curve curve = Curves.easeInCirc,
    Future Function()? function,
    int duration = 2500,
    required dynamic splash,
    required Widget nextScreen,
    Color backgroundColor = Colors.white,
    Animatable? customTween,
    bool centered = true,
    bool disableNavigation = false,
    SplashTransition? splashTransition,
    PageTransitionType? pageTransitionType,
    Duration? animationDuration,
    double? splashIconSize,
    String? nextRoute,
  }) {
    return AnimatedSplashScreen._internal(
      backgroundColor: backgroundColor,
      animationDuration: animationDuration,
      transitionType: pageTransitionType ?? PageTransitionType.bottomToTop,
      splashTransition: splashTransition ?? SplashTransition.fadeTransition,
      splashIconSize: splashIconSize,
      customAnimation: customTween,
      function: function,
      nextRoute: nextRoute,
      duration: duration,
      centered: centered,
      disableNavigation: disableNavigation,
      splash: splash,
      type: SplashType.simpleSplash,
      nextScreen: nextScreen,
      curve: curve,
    );
  }

  factory AnimatedSplashScreen.withScreenFunction({
    Curve curve = Curves.easeInCirc,
    bool centered = true,
    bool disableNavigation = false,
    int duration = 2500,
    required dynamic splash,
    required Future<Widget> Function() screenFunction,
    Animatable? customTween,
    Color backgroundColor = Colors.white,
    SplashTransition? splashTransition,
    PageTransitionType? pageTransitionType,
    Duration? animationDuration,
    double? splashIconSize,
  }) {
    return AnimatedSplashScreen._internal(
      type: SplashType.backgroundScreenReturn,
      animationDuration: animationDuration,
      transitionType: pageTransitionType ?? PageTransitionType.bottomToTop,
      splashTransition: splashTransition ?? SplashTransition.fadeTransition,
      backgroundColor: backgroundColor,
      splashIconSize: splashIconSize,
      customAnimation: customTween,
      function: screenFunction,
      duration: duration,
      centered: centered,
      disableNavigation: disableNavigation,
      nextRoute: null,
      nextScreen: null,
      splash: splash,
      curve: curve,
    );
  }

  factory AnimatedSplashScreen.withScreenRouteFunction({
    Curve curve = Curves.easeInCirc,
    bool centered = true,
    bool disableNavigation = false,
    int duration = 2500,
    required dynamic splash,
    required Future<String> Function() screenRouteFunction,
    Animatable? customTween,
    Color backgroundColor = Colors.white,
    SplashTransition? splashTransition,
    PageTransitionType? pageTransitionType,
    Duration? animationDuration,
    double? splashIconSize,
  }) {
    return AnimatedSplashScreen._internal(
      type: SplashType.backgroundScreenReturn,
      animationDuration: animationDuration,
      transitionType: pageTransitionType ?? PageTransitionType.bottomToTop,
      splashTransition: splashTransition ?? SplashTransition.fadeTransition,
      backgroundColor: backgroundColor,
      splashIconSize: splashIconSize,
      customAnimation: customTween,
      function: screenRouteFunction,
      duration: duration,
      centered: centered,
      disableNavigation: disableNavigation,
      nextRoute: null,
      nextScreen: null,
      splash: splash,
      curve: curve,
    );
  }

  const AnimatedSplashScreen._internal({
    required this.animationDuration,
    required this.splashTransition,
    required this.customAnimation,
    required this.backgroundColor,
    required this.transitionType,
    required this.splashIconSize,
    required this.nextScreen,
    required this.function,
    required this.duration,
    required this.centered,
    required this.disableNavigation,
    required this.splash,
    required this.curve,
    required this.type,
    required this.nextRoute,
  });

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

class _AnimatedSplashScreenState extends State<AnimatedSplashScreen>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  static late BuildContext _context;
  late Animation _animation;

  AnimatedSplashScreen get w => widget;

  @override
  void initState() {
    super.initState();

    _animationController = AnimationController(
      duration: w.animationDuration ?? Duration(milliseconds: 800),
      vsync: this,
    );

    Animatable animation =
        w.customAnimation ??
        () {
              switch (w.splashTransition) {
                case SplashTransition.slideTransition:
                  return Tween<Offset>(end: Offset.zero, begin: Offset(1, 0));

                case SplashTransition.decoratedBoxTransition:
                  return DecorationTween(
                    end: BoxDecoration(color: Colors.black87),
                    begin: BoxDecoration(color: Colors.redAccent),
                  );

                default:
                  return Tween(begin: 0.0, end: 1.0);
              }
            }()
            as Animatable<dynamic>;

    _animation = animation.animate(
      CurvedAnimation(parent: _animationController, curve: w.curve),
    );
    _animationController.forward().then((value) => doTransition());
  }

  /// call function case needed and then jump to next screen
  doTransition() async {
    if (w.type == SplashType.backgroundScreenReturn)
      navigator(await w.function!());
    else if (!w.disableNavigation)
      navigator(w.nextRoute ?? w.nextScreen);
  }

  @override
  void dispose() {
    _animationController.reset();
    _animationController.dispose();
    super.dispose();
  }

  navigator(screen) {
    Future.delayed(
      Duration(milliseconds: w.duration < 100 ? 100 : w.duration),
    ).then((_) {
      try {
        if (screen is String) {
          Navigator.of(_context).pushReplacementNamed(screen);
        } else {
          Navigator.of(_context).pushReplacement(
            PageTransition(type: w.transitionType, child: screen),
          );
        }
      } catch (msg) {
        print(
          'AnimatedSplashScreen -> '
          'error in jump to next screen, probably '
          'this run is in hot reload: $msg',
        );
      }
    });
  }

  /// Return icon of splash screen
  Widget getSplash() {
    final size =
        w.splashIconSize ?? MediaQuery.of(context).size.shortestSide * 0.2;

    Widget main({required Widget child}) =>
        w.centered ? Center(child: child) : child;

    return getTransition(
      child: main(
        child: SizedBox(
          height: size,
          child:
              w.splash is String
                  ? <Widget>() {
                    if (w.splash.toString().contains('[n]'))
                      return Image.network(
                        w.splash.toString().replaceAll('[n]', ''),
                      );
                    else
                      return Image.asset(w.splash);
                  }()
                  : (w.splash is IconData
                      ? Icon(w.splash, size: size)
                      : w.splash),
        ),
      ),
    );
  }

  /// return transtion
  Widget getTransition({required Widget child}) {
    switch (w.splashTransition) {
      case SplashTransition.slideTransition:
        return SlideTransition(
          position: (_animation as Animation<Offset>),
          child: child,
        );

      case SplashTransition.scaleTransition:
        return ScaleTransition(
          scale: (_animation as Animation<double>),
          child: child,
        );

      case SplashTransition.rotationTransition:
        return RotationTransition(
          turns: (_animation as Animation<double>),
          child: child,
        );

      case SplashTransition.sizeTransition:
        return SizeTransition(
          sizeFactor: (_animation as Animation<double>),
          child: child,
        );

      case SplashTransition.fadeTransition:
        return FadeTransition(
          opacity: (_animation as Animation<double>),
          child: child,
        );

      case SplashTransition.decoratedBoxTransition:
        return DecoratedBoxTransition(
          decoration: (_animation as Animation<Decoration>),
          child: child,
        );

      default:
        return FadeTransition(
          opacity: (_animation as Animation<double>),
          child: child,
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    _context = context;

    return Scaffold(backgroundColor: w.backgroundColor, body: getSplash());
  }
}
