Current File : /home/tradevaly/www/fresh/lib/view/screens/menu/widget/custom_drawer.dart |
import 'dart:math' show pi;
import 'package:flutter/material.dart';
import 'package:flutter_grocery/provider/localization_provider.dart';
import 'package:provider/provider.dart';
class CustomDrawerController {
Function open;
Function close;
Function toggle;
Function isOpen;
ValueNotifier<DrawerState> stateNotifier;
}
class CustomDrawer extends StatefulWidget {
CustomDrawer({
this.controller,
@required this.menuScreen,
@required this.mainScreen,
this.slideWidth = 275.0,
this.borderRadius = 16.0,
this.angle = -12.0,
this.backgroundColor = Colors.white,
this.showShadow = false,
this.openCurve,
this.closeCurve,
}) : assert(angle <= 0.0 && angle >= -30.0);
final CustomDrawerController controller;
final Widget menuScreen;
final Widget mainScreen;
final double slideWidth;
final double borderRadius;
final double angle;
final Color backgroundColor;
final bool showShadow;
final Curve openCurve;
final Curve closeCurve;
@override
_CustomDrawerState createState() => new _CustomDrawerState();
/// static function to provide the drawer state
static _CustomDrawerState of(BuildContext context) {
return context.findAncestorStateOfType<State<CustomDrawer>>();
}
/// Static function to determine the device text direction RTL/LTR
static bool isRTL(BuildContext context) {
return !Provider.of<LocalizationProvider>(context, listen: false).isLtr;
}
}
class _CustomDrawerState extends State<CustomDrawer>
with SingleTickerProviderStateMixin {
final Curve _scaleDownCurve = Interval(0.0, 0.3, curve: Curves.easeOut);
final Curve _scaleUpCurve = Interval(0.0, 1.0, curve: Curves.easeOut);
final Curve _slideOutCurve = Interval(0.0, 1.0, curve: Curves.easeOut);
final Curve _slideInCurve =
Interval(0.0, 1.0, curve: Curves.easeOut); // Curves.bounceOut
/// check the slide direction
AnimationController _animationController;
DrawerState _state = DrawerState.closed;
double get _percentOpen => _animationController.value;
open() {
_animationController.forward();
}
close() {
_animationController.reverse();
}
toggle() {
if (_state == DrawerState.open) {
close();
} else if (_state == DrawerState.closed) {
open();
}
}
bool isOpen() =>
_state == DrawerState.open /*|| _state == DrawerState.opening*/;
/// Drawer state
ValueNotifier<DrawerState> stateNotifier;
@override
void initState() {
super.initState();
stateNotifier = ValueNotifier(_state);
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500))
..addStatusListener((AnimationStatus status) {
switch (status) {
case AnimationStatus.forward:
_state = DrawerState.opening;
_updateStatusNotifier();
break;
case AnimationStatus.reverse:
_state = DrawerState.closing;
_updateStatusNotifier();
break;
case AnimationStatus.completed:
_state = DrawerState.open;
_updateStatusNotifier();
break;
case AnimationStatus.dismissed:
_state = DrawerState.closed;
_updateStatusNotifier();
break;
}
});
/// assign controller function to the widget methods
if (widget.controller != null) {
widget.controller.open = open;
widget.controller.close = close;
widget.controller.toggle = toggle;
widget.controller.isOpen = isOpen;
widget.controller.stateNotifier = stateNotifier;
}
}
_updateStatusNotifier() {
stateNotifier.value = _state;
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
Widget _zoomAndSlideContent(Widget container,BuildContext context,
{double angle, double scale, double slide = 0}) {
var slidePercent, scalePercent;
/// determine current slide percent based on the MenuStatus
switch (_state) {
case DrawerState.closed:
slidePercent = 0.0;
scalePercent = 0.0;
break;
case DrawerState.open:
slidePercent = 1.0;
scalePercent = 1.0;
break;
case DrawerState.opening:
slidePercent =
(widget.openCurve ?? _slideOutCurve).transform(_percentOpen);
scalePercent = _scaleDownCurve.transform(_percentOpen);
break;
case DrawerState.closing:
slidePercent =
(widget.closeCurve ?? _slideInCurve).transform(_percentOpen);
scalePercent = _scaleUpCurve.transform(_percentOpen);
break;
}
final int _rtlSlide = CustomDrawer.isRTL(context) ? -1 : 1;
final slideAmount = (widget.slideWidth - slide) * slidePercent * _rtlSlide;
final contentScale = (scale ?? 1.0) - (0.4 * scalePercent);
final cornerRadius = widget.borderRadius * _percentOpen;
final rotationAngle =
(((angle ?? widget.angle) * pi * _rtlSlide) / 180) * _percentOpen;
return Transform(
transform: Matrix4.translationValues(slideAmount, 0, 0)
..rotateZ(rotationAngle)
..scale(contentScale, contentScale),
alignment: Alignment.centerLeft,
child: ClipRRect(
borderRadius: BorderRadius.circular(cornerRadius),
child: container,
),
);
}
@override
Widget build(BuildContext context) {
final slidePercent =
CustomDrawer.isRTL(context) ? MediaQuery.of(context).size.width * .1 : 15.0;
return Stack(
children: [
GestureDetector(
child: widget.menuScreen,
onPanUpdate: (details) {
final bool _rtl = CustomDrawer.isRTL(context);
if (details.delta.dx < -6 && !_rtl ||
details.delta.dx < 6 && _rtl) {
toggle();
}
},
),
if (widget.showShadow) ...[
/// Displaying the first shadow
AnimatedBuilder(
animation: _animationController,
builder: (_, w) => _zoomAndSlideContent(w,context,
angle: (widget.angle == 0.0) ? 0.0 : widget.angle - 8,
scale: .9,
slide: slidePercent * 2),
child: Container(
color: widget.backgroundColor.withAlpha(31),
),
),
/// Displaying the second shadow
AnimatedBuilder(
animation: _animationController,
builder: (_, w) => _zoomAndSlideContent(w,context,
angle: (widget.angle == 0.0) ? 0.0 : widget.angle - 4.0,
scale: .95,
slide: slidePercent),
child: Container(
color: widget.backgroundColor,
),
)
],
/// Displaying the main screen
AnimatedBuilder(
animation: _animationController,
builder: (_, w) => _zoomAndSlideContent(w, context),
child: widget.mainScreen,
),
],
);
}
}
/// Drawer State enum
enum DrawerState { opening, closing, open, closed }