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.
uco-mobile-poc/lib/app/modules/speed_dial/src/simple_speed_dial.dart

373 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:uco_mobile_poc/app/res/app_colors.dart';
class SpeedDial extends StatefulWidget {
const SpeedDial({
Key? key,
required this.child,
required this.speedDialChildren,
this.labelsStyle,
this.labelsBackgroundColor,
this.controller,
this.closedForegroundColor,
this.openForegroundColor,
this.closedBackgroundColor,
this.openBackgroundColor,
}) : super(key: key);
final Widget child;
/// A list of [SpeedDialChild] to display when the [SpeedDial] is open.
final List<SpeedDialChild> speedDialChildren;
/// Specifies the [SpeedDialChild] label text style.
final TextStyle? labelsStyle;
/// The background color of the labels.
final Color? labelsBackgroundColor;
/// An animation controller for the [SpeedDial].
///
/// Provide an [AnimationController] to control the animations
/// from outside the [SpeedDial] widget.
final AnimationController? controller;
/// The color of the [SpeedDial] button foreground when closed.
///
/// The [SpeedDial] foreground will animate to this color when the user
/// closes the speed dial.
final Color? closedForegroundColor;
/// The color of the [SpeedDial] button foreground when opened.
///
/// The [SpeedDial] foreground will animate to this color when the user
/// opens the speed dial.
final Color? openForegroundColor;
/// The color of the [SpeedDial] button background when closed.
///
/// The [SpeedDial] background will animate to this color when the user
/// closes the speed dial.
final Color? closedBackgroundColor;
/// The color of the [SpeedDial] button background when open.
///
/// The [SpeedDial] background will animate to this color when the user
/// opens the speed dial.
final Color? openBackgroundColor;
@override
State<StatefulWidget> createState() {
return _SpeedDialState();
}
}
class _SpeedDialState extends State<SpeedDial>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<Color?> _backgroundColorAnimation;
late Animation<Color?> _foregroundColorAnimation;
final List<Animation<double>> _speedDialChildAnimations =
<Animation<double>>[];
@override
void initState() {
_animationController = widget.controller ??
AnimationController(
vsync: this, duration: const Duration(milliseconds: 100));
_animationController.addListener(() {
if (mounted) {
setState(() {});
}
});
_backgroundColorAnimation = ColorTween(
begin: widget.closedBackgroundColor,
end: widget.openBackgroundColor,
).animate(_animationController);
_foregroundColorAnimation = ColorTween(
begin: widget.closedForegroundColor,
end: widget.openForegroundColor,
).animate(_animationController);
final double fractionOfOneSpeedDialChild =
1.0 / widget.speedDialChildren.length;
for (int speedDialChildIndex = 0;
speedDialChildIndex < widget.speedDialChildren.length;
++speedDialChildIndex) {
final List<TweenSequenceItem<double>> tweenSequenceItems =
<TweenSequenceItem<double>>[];
final double firstWeight =
fractionOfOneSpeedDialChild * speedDialChildIndex;
if (firstWeight > 0.0) {
tweenSequenceItems.add(TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: firstWeight,
));
}
tweenSequenceItems.add(TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: fractionOfOneSpeedDialChild,
));
final double lastWeight = fractionOfOneSpeedDialChild *
(widget.speedDialChildren.length - 1 - speedDialChildIndex);
if (lastWeight > 0.0) {
tweenSequenceItems.add(TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0), weight: lastWeight));
}
_speedDialChildAnimations.insert(
0,
TweenSequence<double>(tweenSequenceItems)
.animate(_animationController));
}
super.initState();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
//
int speedDialChildAnimationIndex = 0;
return _animationController.isDismissed
? GestureDetector(
onTap: () {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
},
child: Container(
width: 60,
height: 60,
clipBehavior: Clip.none,
padding: const EdgeInsets.all(14),
margin: EdgeInsets.only(
top: 18,
right: Directionality.of(context) == TextDirection.ltr ? 16 : 0,
left: Directionality.of(context) == TextDirection.rtl ? 16 : 0,
),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
// borderRadius: BorderRadius.circular(60),
border: Border.all(color: AppColors.colorPrimary, width: 2),
),
child: widget.child,
),
)
: GestureDetector(
onTap: () {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
},
child: OverflowBox(
maxWidth: MediaQuery.sizeOf(context).width + 32,
child: Container(
color: Colors.black.withOpacity(0.4),
width: MediaQuery.sizeOf(context).width + 32,
child: Column(
mainAxisSize: _animationController.isDismissed
? MainAxisSize.max
: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
if (!_animationController.isDismissed)
Padding(
padding: EdgeInsets.only(
right: Directionality.of(context) == TextDirection.ltr
? 24
: 0,
left: Directionality.of(context) == TextDirection.rtl
? 24
: 0,
),
child: GestureDetector(
onTap: () {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: widget.speedDialChildren
.map<Widget>((SpeedDialChild speedDialChild) {
final Widget speedDialChildWidget = Opacity(
opacity: _speedDialChildAnimations[
speedDialChildAnimationIndex]
.value,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
if (speedDialChild.label != null)
Padding(
padding: const EdgeInsets.only(
right: 16.0 - 4.0),
child: GestureDetector(
onTap: () => _onTap(speedDialChild),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0),
child: Text(
speedDialChild.label!,
style: widget.labelsStyle,
),
),
),
),
ScaleTransition(
scale: _speedDialChildAnimations[
speedDialChildAnimationIndex],
child: GestureDetector(
onTap: () => _onTap(speedDialChild),
child: Container(
width: 45,
height: 45,
clipBehavior: Clip.none,
padding: const EdgeInsets.all(10),
margin: EdgeInsets.only(
top: 18,
right: Directionality.of(context) ==
TextDirection.ltr
? 14
: 0,
left: Directionality.of(context) ==
TextDirection.rtl
? 14
: 0,
),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
// borderRadius: BorderRadius.circular(60),
border: Border.all(
color: AppColors.colorPrimary,
width: 2),
),
child: speedDialChild.child,
),
),
),
],
),
);
speedDialChildAnimationIndex++;
return speedDialChildWidget;
}).toList(),
),
),
),
GestureDetector(
onTap: () {
if (_animationController.isDismissed) {
_animationController.forward();
} else {
_animationController.reverse();
}
},
child: Container(
width: 60,
height: 60,
clipBehavior: Clip.none,
padding: const EdgeInsets.all(14),
margin: EdgeInsets.only(
top: 18,
right: Directionality.of(context) == TextDirection.ltr
? 32
: 0,
left: Directionality.of(context) == TextDirection.rtl
? 32
: 0,
),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
// borderRadius: BorderRadius.circular(60),
border: Border.all(
color: AppColors.colorPrimary, width: 2),
),
child: widget.child,
),
),
SizedBox(height: 24),
],
),
),
),
);
}
void _onTap(SpeedDialChild speedDialChild) {
if (speedDialChild.closeSpeedDialOnPressed) {
_animationController.reverse();
}
speedDialChild.onPressed.call();
}
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 4.0),
// child: FloatingActionButton(
// heroTag: speedDialChildAnimationIndex,
// mini: true,
// foregroundColor: speedDialChild.foregroundColor,
// backgroundColor: speedDialChild.backgroundColor,
// onPressed: () => _onTap(speedDialChild),
// child: speedDialChild.child,
// ),
// )
}
class SpeedDialChild {
const SpeedDialChild({
required this.child,
required this.onPressed,
this.foregroundColor,
this.backgroundColor,
this.label,
this.closeSpeedDialOnPressed = true,
});
/// A widget to display as the [SpeedDialChild].
final Widget child;
/// A callback to be executed when the [SpeedDialChild] is pressed.
final Function onPressed;
/// The [SpeedDialChild] foreground color.
final Color? foregroundColor;
/// The [SpeedDialChild] background color.
final Color? backgroundColor;
/// The text displayed next to the [SpeedDialChild] when the [SpeedDial] is open.
final String? label;
/// Whether the [SpeedDial] should close after the [onPressed] callback of
/// [SpeedDialChild] is called.
///
/// Defaults to [true].
final bool closeSpeedDialOnPressed;
}