/* * Copyright © 2020, Simform Solutions * All rights reserved. * https://github.com/simformsolutions/flutter_showcaseview */ /* Customized By: Ibrahim Albitar */ import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'app_anchored_overlay_widget.dart'; import 'app_get_position.dart'; import 'app_shape_painter.dart'; import 'app_showcase_widget.dart'; import 'app_tool_tip_widget.dart'; class AppShowcase extends StatefulWidget { final Widget child; final String title; final String description; final ShapeBorder shapeBorder; final TextStyle titleTextStyle; final TextStyle descTextStyle; final GlobalKey key; final Color overlayColor; final double overlayOpacity; final Widget container; final Color showcaseBackgroundColor; final Color textColor; final bool showArrow; final double height; final double width; final Duration animationDuration; final VoidCallback onToolTipClick; final VoidCallback onTargetClick; final VoidCallback onSkipClick; final bool disposeOnTap; final bool disableAnimation; const AppShowcase( {@required this.key, @required this.child, this.title, @required this.description, this.shapeBorder, this.overlayColor = Colors.black, this.overlayOpacity = 0.75, this.titleTextStyle, this.descTextStyle, this.showcaseBackgroundColor = Colors.white, this.textColor = Colors.black, this.showArrow = true, this.onTargetClick, this.onSkipClick, this.disposeOnTap, this.animationDuration = const Duration(milliseconds: 2000), this.disableAnimation = false}) : height = null, width = null, container = null, this.onToolTipClick = null, assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0, "overlay opacity should be >= 0.0 and <= 1.0."), assert( onTargetClick == null ? true : (disposeOnTap == null ? false : true), "disposeOnTap is required if you're using onTargetClick"), assert( disposeOnTap == null ? true : (onTargetClick == null ? false : true), "onTargetClick is required if you're using disposeOnTap"), assert(key != null || child != null || title != null || showArrow != null || description != null || shapeBorder != null || overlayColor != null || titleTextStyle != null || descTextStyle != null || showcaseBackgroundColor != null || textColor != null || shapeBorder != null || animationDuration != null); const AppShowcase.withWidget( {this.key, @required this.child, @required this.container, @required this.height, @required this.width, this.title, this.description, this.shapeBorder, this.overlayColor = Colors.black, this.overlayOpacity = 0.75, this.titleTextStyle, this.descTextStyle, this.showcaseBackgroundColor = Colors.white, this.textColor = Colors.black, this.onTargetClick, this.onSkipClick, this.disposeOnTap, this.animationDuration = const Duration(milliseconds: 2000), this.disableAnimation = false}) : this.showArrow = false, this.onToolTipClick = null, assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0, "overlay opacity should be >= 0.0 and <= 1.0."), assert(key != null || child != null || title != null || description != null || shapeBorder != null || overlayColor != null || titleTextStyle != null || descTextStyle != null || showcaseBackgroundColor != null || textColor != null || shapeBorder != null || animationDuration != null); @override _AppShowcaseState createState() => _AppShowcaseState(); } class _AppShowcaseState extends State with TickerProviderStateMixin { bool _showShowCase = false; Animation _slideAnimation; AnimationController _slideAnimationController; GetPosition position; @override void initState() { super.initState(); _slideAnimationController = AnimationController( duration: widget.animationDuration, vsync: this, )..addStatusListener((AnimationStatus status) { if (status == AnimationStatus.completed) { _slideAnimationController.reverse(); } if (_slideAnimationController.isDismissed) { if (!widget.disableAnimation) { _slideAnimationController.forward(); } } }); _slideAnimation = CurvedAnimation( parent: _slideAnimationController, curve: Curves.easeInOut, ); position = GetPosition(key: widget.key); } @override void dispose() { _slideAnimationController.dispose(); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); showOverlay(); } /// /// show overlay if there is any target widget /// void showOverlay() { GlobalKey activeStep = ShowCaseWidget.activeTargetWidget(context); setState(() { _showShowCase = activeStep == widget.key; }); if (activeStep == widget.key) { if (!widget.disableAnimation) { _slideAnimationController.forward(); } } } @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return AnchoredOverlay( overlayBuilder: (BuildContext context, Rect rectBound, Offset offset) => buildOverlayOnTarget(offset, rectBound.size, rectBound, size), showOverlay: true, child: widget.child, ); } _nextIfAny() { ShowCaseWidget.of(context).completed(widget.key); if (!widget.disableAnimation) { _slideAnimationController.forward(); } } _getOnTargetTap() { if (widget.disposeOnTap == true) { return widget.onTargetClick == null ? () { ShowCaseWidget.of(context).dismiss(); } : () { ShowCaseWidget.of(context).dismiss(); widget.onTargetClick(); }; } else { return widget.onTargetClick ?? _nextIfAny; } } _getOnTooltipTap() { if (widget.disposeOnTap == true) { return widget.onToolTipClick == null ? () { ShowCaseWidget.of(context).dismiss(); } : () { ShowCaseWidget.of(context).dismiss(); widget.onToolTipClick(); }; } else { return widget.onToolTipClick ?? () {}; } } buildOverlayOnTarget( Offset offset, Size size, Rect rectBound, Size screenSize, ) => Visibility( visible: _showShowCase, maintainAnimation: true, maintainState: true, child: Stack( children: [ GestureDetector( onTap: _nextIfAny, child: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: CustomPaint( painter: ShapePainter( opacity: widget.overlayOpacity, rect: position.getRect(), shapeBorder: widget.shapeBorder, color: widget.overlayColor), ), ), ), _TargetWidget( offset: offset, size: size, onTap: _getOnTargetTap(), shapeBorder: widget.shapeBorder, ), AppToolTipWidget( position: position, offset: offset, screenSize: screenSize, title: widget.title, description: widget.description, animationOffset: _slideAnimation, titleTextStyle: widget.titleTextStyle, descTextStyle: widget.descTextStyle, container: widget.container, tooltipColor: widget.showcaseBackgroundColor, textColor: widget.textColor, showArrow: widget.showArrow, contentHeight: widget.height, contentWidth: widget.width, onTooltipTap: _getOnTooltipTap(), ), GestureDetector( child: AppText( "Skip", color: Colors.white, fontSize: 20, marginRight: 15, marginLeft: 15, marginTop: 15, ), onTap: widget.onSkipClick) ], ), ); } class _TargetWidget extends StatelessWidget { final Offset offset; final Size size; final Animation widthAnimation; final VoidCallback onTap; final ShapeBorder shapeBorder; _TargetWidget({ Key key, @required this.offset, this.size, this.widthAnimation, this.onTap, this.shapeBorder, }) : super(key: key); @override Widget build(BuildContext context) { return Positioned( top: offset.dy, left: offset.dx, child: FractionalTranslation( translation: const Offset(-0.5, -0.5), child: GestureDetector( onTap: onTap, child: Container( height: size.height + 16, width: size.width + 16, decoration: ShapeDecoration( shape: shapeBorder ?? RoundedRectangleBorder( borderRadius: const BorderRadius.all( Radius.circular(8), ), ), ), ), ), ), ); } }