You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
doctor_app_flutter/lib/widgets/shared/user-guid/app_showcase.dart

350 lines
9.7 KiB
Dart

/*
* 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<AppShowcase>
with TickerProviderStateMixin {
bool _showShowCase = false;
Animation<double> _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<double> 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),
),
),
),
),
),
),
);
}
}