Add User Guid Module
parent
4fe69599fb
commit
98950890a0
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright © 2020, Simform Solutions
|
||||
* All rights reserved.
|
||||
* https://github.com/simformsolutions/flutter_showcaseview
|
||||
*/
|
||||
|
||||
/*
|
||||
Customized By: Ibrahim Albitar
|
||||
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Displays an overlay Widget anchored directly above the center of this
|
||||
/// [AnchoredOverlay].
|
||||
///
|
||||
/// The overlay Widget is created by invoking the provided [overlayBuilder].
|
||||
///
|
||||
/// The [anchor] position is provided to the [overlayBuilder], but the builder
|
||||
/// does not have to respect it. In other words, the [overlayBuilder] can
|
||||
/// interpret the meaning of "anchor" however it wants - the overlay will not
|
||||
/// be forced to be centered about the [anchor].
|
||||
///
|
||||
/// The overlay built by this [AnchoredOverlay] can be conditionally shown
|
||||
/// and hidden by settings the [showOverlay] property to true or false.
|
||||
///
|
||||
/// The [overlayBuilder] is invoked every time this Widget is rebuilt.
|
||||
///
|
||||
class AnchoredOverlay extends StatelessWidget {
|
||||
final bool showOverlay;
|
||||
final Widget Function(BuildContext, Rect anchorBounds, Offset anchor)
|
||||
overlayBuilder;
|
||||
final Widget child;
|
||||
|
||||
AnchoredOverlay({
|
||||
key,
|
||||
this.showOverlay = false,
|
||||
this.overlayBuilder,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return OverlayBuilder(
|
||||
showOverlay: showOverlay,
|
||||
overlayBuilder: (BuildContext overlayContext) {
|
||||
// To calculate the "anchor" point we grab the render box of
|
||||
// our parent Container and then we find the center of that box.
|
||||
RenderBox box = context.findRenderObject() as RenderBox;
|
||||
final topLeft =
|
||||
box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
final bottomRight =
|
||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
final Rect anchorBounds = Rect.fromLTRB(
|
||||
topLeft.dx,
|
||||
topLeft.dy,
|
||||
bottomRight.dx,
|
||||
bottomRight.dy,
|
||||
);
|
||||
final anchorCenter = box.size.center(topLeft);
|
||||
return overlayBuilder(overlayContext, anchorBounds, anchorCenter);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Displays an overlay Widget as constructed by the given [overlayBuilder].
|
||||
//
|
||||
// The overlay built by the [overlayBuilder] can be conditionally shown and hidden by settings the [showOverlay]
|
||||
// property to true or false.
|
||||
//
|
||||
// The [overlayBuilder] is invoked every time this Widget is rebuilt.
|
||||
//
|
||||
// Implementation note: the reason we rebuild the overlay every time our state changes is because there doesn't seem
|
||||
// to be any better way to invalidate the overlay itself than to invalidate this Widget.
|
||||
// Remember, overlay Widgets exist in [OverlayEntry]s which are inaccessible to outside Widgets.
|
||||
// But if a better approach is found then feel free to use it.
|
||||
//
|
||||
class OverlayBuilder extends StatefulWidget {
|
||||
final bool showOverlay;
|
||||
final Widget Function(BuildContext) overlayBuilder;
|
||||
final Widget child;
|
||||
|
||||
OverlayBuilder({
|
||||
key,
|
||||
this.showOverlay = false,
|
||||
this.overlayBuilder,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_OverlayBuilderState createState() => _OverlayBuilderState();
|
||||
}
|
||||
|
||||
class _OverlayBuilderState extends State<OverlayBuilder> {
|
||||
OverlayEntry _overlayEntry;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.showOverlay) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => showOverlay());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(OverlayBuilder oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
|
||||
}
|
||||
|
||||
@override
|
||||
void reassemble() {
|
||||
super.reassemble();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (isShowingOverlay()) {
|
||||
hideOverlay();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool isShowingOverlay() => _overlayEntry != null;
|
||||
|
||||
void showOverlay() {
|
||||
if (_overlayEntry == null) {
|
||||
// Create the overlay.
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: widget.overlayBuilder,
|
||||
);
|
||||
addToOverlay(_overlayEntry);
|
||||
} else {
|
||||
// Rebuild overlay.
|
||||
buildOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void addToOverlay(OverlayEntry overlayEntry) async {
|
||||
Overlay.of(context).insert(overlayEntry);
|
||||
final overlay = Overlay.of(context);
|
||||
if (overlayEntry == null)
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => overlay.insert(overlayEntry));
|
||||
}
|
||||
|
||||
void hideOverlay() {
|
||||
if (_overlayEntry != null) {
|
||||
_overlayEntry.remove();
|
||||
_overlayEntry = null;
|
||||
}
|
||||
}
|
||||
|
||||
void syncWidgetAndOverlay() {
|
||||
if (isShowingOverlay() && !widget.showOverlay) {
|
||||
hideOverlay();
|
||||
} else if (!isShowingOverlay() && widget.showOverlay) {
|
||||
showOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void buildOverlay() async {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => _overlayEntry?.markNeedsBuild());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
buildOverlay();
|
||||
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright © 2020, Simform Solutions
|
||||
* All rights reserved.
|
||||
* https://github.com/simformsolutions/flutter_showcaseview
|
||||
*/
|
||||
|
||||
/*
|
||||
Customized By: Ibrahim Albitar
|
||||
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GetPosition {
|
||||
final GlobalKey key;
|
||||
|
||||
GetPosition({this.key});
|
||||
|
||||
Rect getRect() {
|
||||
RenderBox box = key.currentContext.findRenderObject();
|
||||
|
||||
final topLeft = box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
final bottomRight =
|
||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
|
||||
Rect rect = Rect.fromLTRB(
|
||||
topLeft.dx,
|
||||
topLeft.dy,
|
||||
bottomRight.dx,
|
||||
bottomRight.dy,
|
||||
);
|
||||
return rect;
|
||||
}
|
||||
|
||||
///Get the bottom position of the widget
|
||||
double getBottom() {
|
||||
RenderBox box = key.currentContext.findRenderObject();
|
||||
final bottomRight =
|
||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
return bottomRight.dy;
|
||||
}
|
||||
|
||||
///Get the top position of the widget
|
||||
double getTop() {
|
||||
RenderBox box = key.currentContext.findRenderObject();
|
||||
final topLeft = box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
return topLeft.dy;
|
||||
}
|
||||
|
||||
///Get the left position of the widget
|
||||
double getLeft() {
|
||||
RenderBox box = key.currentContext.findRenderObject();
|
||||
final topLeft = box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
return topLeft.dx;
|
||||
}
|
||||
|
||||
///Get the right position of the widget
|
||||
double getRight() {
|
||||
RenderBox box = key.currentContext.findRenderObject();
|
||||
final bottomRight =
|
||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
||||
return bottomRight.dx;
|
||||
}
|
||||
|
||||
double getHeight() {
|
||||
return getBottom() - getTop();
|
||||
}
|
||||
|
||||
double getWidth() {
|
||||
return getRight() - getLeft();
|
||||
}
|
||||
|
||||
double getCenter() {
|
||||
return (getLeft() + getRight()) / 2;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright © 2020, Simform Solutions
|
||||
* All rights reserved.
|
||||
* https://github.com/simformsolutions/flutter_showcaseview
|
||||
*/
|
||||
|
||||
/*
|
||||
Customized By: Ibrahim Albitar
|
||||
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ShapePainter extends CustomPainter {
|
||||
Rect rect;
|
||||
final ShapeBorder shapeBorder;
|
||||
final Color color;
|
||||
final double opacity;
|
||||
|
||||
ShapePainter({
|
||||
@required this.rect,
|
||||
this.color,
|
||||
this.shapeBorder,
|
||||
this.opacity,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint();
|
||||
paint.color = color.withOpacity(opacity);
|
||||
RRect outer =
|
||||
RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(0));
|
||||
|
||||
double radius = shapeBorder == CircleBorder() ? 50 : 3;
|
||||
|
||||
RRect inner = RRect.fromRectAndRadius(rect, Radius.circular(radius));
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@ -0,0 +1,349 @@
|
||||
/*
|
||||
* 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright © 2020, Simform Solutions
|
||||
* All rights reserved.
|
||||
* https://github.com/simformsolutions/flutter_showcaseview
|
||||
*/
|
||||
|
||||
/*
|
||||
Customized By: Ibrahim Albitar
|
||||
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ShowCaseWidget extends StatefulWidget {
|
||||
final Builder builder;
|
||||
final VoidCallback onFinish;
|
||||
|
||||
const ShowCaseWidget({@required this.builder, this.onFinish});
|
||||
|
||||
static activeTargetWidget(BuildContext context) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<_InheritedShowCaseView>()
|
||||
.activeWidgetIds;
|
||||
}
|
||||
|
||||
static ShowCaseWidgetState of(BuildContext context) {
|
||||
ShowCaseWidgetState state =
|
||||
context.findAncestorStateOfType<ShowCaseWidgetState>();
|
||||
if (state != null) {
|
||||
return context.findAncestorStateOfType<ShowCaseWidgetState>();
|
||||
} else {
|
||||
throw Exception('Please provide ShowCaseView context');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ShowCaseWidgetState createState() => ShowCaseWidgetState();
|
||||
}
|
||||
|
||||
class ShowCaseWidgetState extends State<ShowCaseWidget> {
|
||||
List<GlobalKey> ids;
|
||||
int activeWidgetId;
|
||||
|
||||
void startShowCase(List<GlobalKey> widgetIds) {
|
||||
setState(() {
|
||||
this.ids = widgetIds;
|
||||
activeWidgetId = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void completed(GlobalKey id) {
|
||||
if (ids != null && ids[activeWidgetId] == id) {
|
||||
setState(() {
|
||||
++activeWidgetId;
|
||||
|
||||
if (activeWidgetId >= ids.length) {
|
||||
_cleanupAfterSteps();
|
||||
if (widget.onFinish != null) {
|
||||
widget.onFinish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void dismiss() {
|
||||
setState(() {
|
||||
_cleanupAfterSteps();
|
||||
});
|
||||
}
|
||||
|
||||
void _cleanupAfterSteps() {
|
||||
ids = null;
|
||||
activeWidgetId = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _InheritedShowCaseView(
|
||||
child: widget.builder,
|
||||
activeWidgetIds: ids?.elementAt(activeWidgetId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InheritedShowCaseView extends InheritedWidget {
|
||||
final GlobalKey activeWidgetIds;
|
||||
|
||||
_InheritedShowCaseView({
|
||||
@required this.activeWidgetIds,
|
||||
@required child,
|
||||
}) : super(child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_InheritedShowCaseView oldWidget) =>
|
||||
oldWidget.activeWidgetIds != activeWidgetIds;
|
||||
}
|
||||
@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright © 2020, Simform Solutions
|
||||
* All rights reserved.
|
||||
* https://github.com/simformsolutions/flutter_showcaseview
|
||||
*/
|
||||
|
||||
/*
|
||||
Customized By: Ibrahim Albitar
|
||||
|
||||
*/
|
||||
|
||||
import 'package:doctor_app_flutter/icons_app/doctor_app_icons.dart';
|
||||
import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'app_get_position.dart';
|
||||
|
||||
class AppToolTipWidget extends StatelessWidget {
|
||||
final GetPosition position;
|
||||
final Offset offset;
|
||||
final Size screenSize;
|
||||
final String title;
|
||||
final String description;
|
||||
final Animation<double> animationOffset;
|
||||
final TextStyle titleTextStyle;
|
||||
final TextStyle descTextStyle;
|
||||
final Widget container;
|
||||
final Color tooltipColor;
|
||||
final Color textColor;
|
||||
final bool showArrow;
|
||||
final double contentHeight;
|
||||
final double contentWidth;
|
||||
static bool isArrowUp;
|
||||
final VoidCallback onTooltipTap;
|
||||
|
||||
AppToolTipWidget({
|
||||
this.position,
|
||||
this.offset,
|
||||
this.screenSize,
|
||||
this.title,
|
||||
this.description,
|
||||
this.animationOffset,
|
||||
this.titleTextStyle,
|
||||
this.descTextStyle,
|
||||
this.container,
|
||||
this.tooltipColor,
|
||||
this.textColor,
|
||||
this.showArrow,
|
||||
this.contentHeight,
|
||||
this.contentWidth,
|
||||
this.onTooltipTap,
|
||||
});
|
||||
|
||||
bool isCloseToTopOrBottom(Offset position) {
|
||||
double height = 120;
|
||||
if (contentHeight != null) {
|
||||
height = contentHeight;
|
||||
}
|
||||
return (screenSize.height - position.dy) <= height;
|
||||
}
|
||||
|
||||
String findPositionForContent(Offset position) {
|
||||
if (isCloseToTopOrBottom(position)) {
|
||||
return 'ABOVE';
|
||||
} else {
|
||||
return 'BELOW';
|
||||
}
|
||||
}
|
||||
|
||||
double _getTooltipWidth() {
|
||||
double titleLength = title == null ? 0 : (title.length * 10.0);
|
||||
double descriptionLength = (description.length * 7.0);
|
||||
if (titleLength > descriptionLength) {
|
||||
return titleLength + 10;
|
||||
} else {
|
||||
return descriptionLength + 10;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isLeft() {
|
||||
double screenWidth = screenSize.width / 3;
|
||||
return !(screenWidth <= position.getCenter());
|
||||
}
|
||||
|
||||
bool _isRight() {
|
||||
double screenWidth = screenSize.width / 3;
|
||||
return ((screenWidth * 2) <= position.getCenter());
|
||||
}
|
||||
|
||||
double _getLeft() {
|
||||
if (_isLeft()) {
|
||||
double leftPadding = position.getCenter() - (_getTooltipWidth() * 0.1);
|
||||
if (leftPadding + _getTooltipWidth() > screenSize.width) {
|
||||
leftPadding = (screenSize.width - 20) - _getTooltipWidth();
|
||||
}
|
||||
if (leftPadding < 20) {
|
||||
leftPadding = 14;
|
||||
}
|
||||
return leftPadding;
|
||||
} else if (!(_isRight())) {
|
||||
return position.getCenter() - (_getTooltipWidth() * 0.5);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
double _getRight() {
|
||||
if (_isRight()) {
|
||||
double rightPadding = position.getCenter() + (_getTooltipWidth() / 2);
|
||||
if (rightPadding + _getTooltipWidth() > screenSize.width) {
|
||||
rightPadding = 14;
|
||||
}
|
||||
return rightPadding;
|
||||
} else if (!(_isLeft())) {
|
||||
return position.getCenter() - (_getTooltipWidth() * 0.5);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
double _getSpace() {
|
||||
double space = position.getCenter() - (contentWidth / 2);
|
||||
if (space + contentWidth > screenSize.width) {
|
||||
space = screenSize.width - contentWidth - 8;
|
||||
} else if (space < (contentWidth / 2)) {
|
||||
space = 16;
|
||||
}
|
||||
return space;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final contentOrientation = findPositionForContent(offset);
|
||||
final contentOffsetMultiplier = contentOrientation == "BELOW" ? 1.0 : -1.0;
|
||||
isArrowUp = contentOffsetMultiplier == 1.0 ? true : false;
|
||||
|
||||
final contentY = isArrowUp
|
||||
? position.getBottom() + (contentOffsetMultiplier * 3)
|
||||
: position.getTop() + (contentOffsetMultiplier * 3);
|
||||
|
||||
final contentFractionalOffset = contentOffsetMultiplier.clamp(-1.0, 0.0);
|
||||
|
||||
double paddingTop = isArrowUp ? 22 : 0;
|
||||
double paddingBottom = isArrowUp ? 0 : 27;
|
||||
|
||||
if (!showArrow) {
|
||||
paddingTop = 10;
|
||||
paddingBottom = 10;
|
||||
}
|
||||
|
||||
if (container == null) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
showArrow ? _getArrow(contentOffsetMultiplier) : Container(),
|
||||
Positioned(
|
||||
top: contentY,
|
||||
left: _getLeft(),
|
||||
right: _getRight(),
|
||||
child: FractionalTranslation(
|
||||
translation: Offset(0.0, contentFractionalOffset),
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: Offset(0.0, contentFractionalOffset / 10),
|
||||
end: Offset(0.0, 0.100),
|
||||
).animate(animationOffset),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
padding:
|
||||
EdgeInsets.only(top: paddingTop, bottom: paddingBottom),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: GestureDetector(
|
||||
onTap: onTooltipTap,
|
||||
child: Container(
|
||||
width: _getTooltipWidth(),
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
color: tooltipColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: title != null
|
||||
? CrossAxisAlignment.start
|
||||
: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
title != null
|
||||
? Row(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
DoctorApp.search_patient),
|
||||
),
|
||||
AppText(
|
||||
title,
|
||||
color: textColor,
|
||||
margin: 2,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
],
|
||||
)
|
||||
: Container(),
|
||||
AppText(
|
||||
description,
|
||||
color: textColor,
|
||||
margin: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Positioned(
|
||||
left: _getSpace(),
|
||||
top: contentY - 10,
|
||||
child: FractionalTranslation(
|
||||
translation: Offset(0.0, contentFractionalOffset),
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: Offset(0.0, contentFractionalOffset / 5),
|
||||
end: Offset(0.0, 0.100),
|
||||
).animate(animationOffset),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: onTooltipTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: paddingTop,
|
||||
),
|
||||
color: Colors.transparent,
|
||||
child: Center(
|
||||
child: container,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _getArrow(contentOffsetMultiplier) {
|
||||
final contentFractionalOffset = contentOffsetMultiplier.clamp(-1.0, 0.0);
|
||||
return Positioned(
|
||||
top: isArrowUp ? position.getBottom() : position.getTop() - 1,
|
||||
left: position.getCenter() - 24,
|
||||
child: FractionalTranslation(
|
||||
translation: Offset(0.0, contentFractionalOffset),
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: Offset(0.0, contentFractionalOffset / 5),
|
||||
end: Offset(0.0, 0.150),
|
||||
).animate(animationOffset),
|
||||
child: isArrowUp
|
||||
? Icon(
|
||||
Icons.arrow_drop_up,
|
||||
color: tooltipColor,
|
||||
size: 50,
|
||||
)
|
||||
: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: tooltipColor,
|
||||
size: 50,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue