Skip to content

Commit 79be426

Browse files
PRBaraiyaami-b-simform
authored andcommitted
✨ G-2-T3230: auto open floating action button
1 parent 3d686ff commit 79be426

2 files changed

Lines changed: 134 additions & 104 deletions

File tree

‎lib/src/speed_dial.dart‎

Lines changed: 129 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import 'background_overlay.dart';
1212
import 'speed_dial_child.dart';
1313
import 'speed_dial_direction.dart';
1414

15-
typedef AsyncChildrenBuilder = Future<List<SpeedDialChild>> Function(
16-
BuildContext context);
15+
typedef AsyncChildrenBuilder =
16+
Future<List<SpeedDialChild>> Function(BuildContext context);
1717

1818
/// Builds the Speed Dial
1919
class SpeedDial extends StatefulWidget {
@@ -159,7 +159,11 @@ class SpeedDial extends StatefulWidget {
159159
/// that was specific to FAB before like onPress, you will have to provide
160160
/// it again to your dialRoot button.
161161
final Widget Function(
162-
BuildContext context, bool open, VoidCallback toggleChildren)? dialRoot;
162+
BuildContext context,
163+
bool open,
164+
VoidCallback toggleChildren,
165+
)?
166+
dialRoot;
163167

164168
/// This is the child of the FAB, if specified it will ignore icon, activeIcon.
165169
final Widget? child;
@@ -234,10 +238,10 @@ class SpeedDial extends StatefulWidget {
234238
}) : super(key: key);
235239

236240
@override
237-
State createState() => _SpeedDialState();
241+
State createState() => SpeedDialState();
238242
}
239243

240-
class _SpeedDialState extends State<SpeedDial>
244+
class SpeedDialState extends State<SpeedDial>
241245
with SingleTickerProviderStateMixin {
242246
late final AnimationController _controller = AnimationController(
243247
duration: widget.animationDuration,
@@ -252,15 +256,16 @@ class _SpeedDialState extends State<SpeedDial>
252256
@override
253257
void initState() {
254258
super.initState();
255-
widget.openCloseDial?.addListener(_onOpenCloseDial);
256259
Future.delayed(Duration.zero, () async {
257260
if (mounted && widget.isOpenOnStart) _toggleChildren();
258261
});
259262
_checkChildren();
263+
widget.openCloseDial?.addListener(_onOpenCloseDialChanged);
260264
}
261265

262266
@override
263267
void dispose() {
268+
widget.openCloseDial?.removeListener(_onOpenCloseDialChanged);
264269
if (overlayEntry != null) {
265270
if (overlayEntry!.mounted) overlayEntry!.remove();
266271
overlayEntry!.dispose();
@@ -270,7 +275,6 @@ class _SpeedDialState extends State<SpeedDial>
270275
backgroundOverlay!.dispose();
271276
}
272277
_controller.dispose();
273-
widget.openCloseDial?.removeListener(_onOpenCloseDial);
274278
super.dispose();
275279
}
276280

@@ -279,27 +283,41 @@ class _SpeedDialState extends State<SpeedDial>
279283
if (oldWidget.children.length != widget.children.length) {
280284
_controller.duration = widget.animationDuration;
281285
}
282-
283-
widget.openCloseDial?.removeListener(_onOpenCloseDial);
284-
widget.openCloseDial?.addListener(_onOpenCloseDial);
286+
if (oldWidget.openCloseDial != widget.openCloseDial) {
287+
oldWidget.openCloseDial?.removeListener(_onOpenCloseDialChanged);
288+
widget.openCloseDial?.addListener(_onOpenCloseDialChanged);
289+
}
285290
super.didUpdateWidget(oldWidget);
286291
}
287292

288-
void _checkChildren() {
289-
if (widget.children.length > 5) {
290-
debugPrint(
291-
'Warning ! You are using more than 5 children, which is not compliant with Material design specs.');
292-
}
293+
/// Opens the dial programmatically if it is currently closed.
294+
void openDial() {
295+
if (!_open) _toggleChildren();
293296
}
294297

295-
void _onOpenCloseDial() {
296-
final show = widget.openCloseDial?.value;
297-
if (!mounted) return;
298-
if (_open != show) {
298+
/// Closes the dial programmatically if it is currently open.
299+
void closeDial() {
300+
if (_open) _toggleChildren();
301+
}
302+
303+
/// Responds to external open/close requests via [SpeedDial.openCloseDial].
304+
///
305+
/// Toggles the dial whenever the notifier value diverges from the current
306+
/// open state (open when `true`, close when `false`).
307+
void _onOpenCloseDialChanged() {
308+
if (widget.openCloseDial!.value != _open) {
299309
_toggleChildren();
300310
}
301311
}
302312

313+
void _checkChildren() {
314+
if (widget.children.length > 5) {
315+
debugPrint(
316+
'Warning ! You are using more than 5 children, which is not compliant with Material design specs.',
317+
);
318+
}
319+
}
320+
303321
void _toggleChildren() async {
304322
if (!mounted) return;
305323

@@ -320,7 +338,7 @@ class _SpeedDialState extends State<SpeedDial>
320338
}
321339
}
322340

323-
toggleOverlay() {
341+
void toggleOverlay() {
324342
if (_open) {
325343
_controller.reverse().whenComplete(() {
326344
overlayEntry?.remove();
@@ -359,7 +377,8 @@ class _SpeedDialState extends State<SpeedDial>
359377
onTap: _toggleChildren,
360378
// (_open && !widget.closeManually) ? _toggleChildren : null,
361379
animation: _controller,
362-
color: widget.overlayColor ??
380+
color:
381+
widget.overlayColor ??
363382
(dark ? Colors.grey[900] : Colors.white)!,
364383
opacity: widget.overlayOpacity,
365384
);
@@ -370,8 +389,8 @@ class _SpeedDialState extends State<SpeedDial>
370389
if (!mounted) return;
371390

372391
_controller.forward();
373-
if (widget.renderOverlay) Overlay.of(context)?.insert(backgroundOverlay!);
374-
Overlay.of(context)?.insert(overlayEntry!);
392+
if (widget.renderOverlay) Overlay.of(context).insert(backgroundOverlay!);
393+
Overlay.of(context).insert(overlayEntry!);
375394
}
376395

377396
if (!mounted) return;
@@ -401,72 +420,76 @@ class _SpeedDialState extends State<SpeedDial>
401420
builder: (BuildContext context, _) => Transform.rotate(
402421
angle:
403422
(widget.activeChild != null || widget.activeIcon != null) &&
404-
widget.useRotationAnimation
405-
? _controller.value * widget.animationAngle
406-
: 0,
423+
widget.useRotationAnimation
424+
? _controller.value * widget.animationAngle
425+
: 0,
407426
child: AnimatedSwitcher(
408-
duration: widget.animationDuration,
409-
child: (widget.child != null && _controller.value < 0.4)
410-
? widget.child
411-
: (widget.activeIcon == null &&
412-
widget.activeChild == null ||
413-
_controller.value < 0.4)
414-
? Container(
427+
duration: widget.animationDuration,
428+
child: (widget.child != null && _controller.value < 0.4)
429+
? widget.child
430+
: (widget.activeIcon == null &&
431+
widget.activeChild == null ||
432+
_controller.value < 0.4)
433+
? Container(
434+
decoration: BoxDecoration(
435+
shape: widget.gradientBoxShape,
436+
gradient: widget.gradient,
437+
),
438+
child: Center(
439+
child: widget.icon != null
440+
? Icon(
441+
widget.icon,
442+
key: const ValueKey<int>(0),
443+
color: widget.iconTheme?.color,
444+
size: widget.iconTheme?.size,
445+
)
446+
: widget.child,
447+
),
448+
)
449+
: Transform.rotate(
450+
angle: widget.useRotationAnimation ? -pi * 1 / 2 : 0,
451+
child:
452+
widget.activeChild ??
453+
Container(
415454
decoration: BoxDecoration(
416455
shape: widget.gradientBoxShape,
417456
gradient: widget.gradient,
418457
),
419458
child: Center(
420-
child: widget.icon != null
421-
? Icon(
422-
widget.icon,
423-
key: const ValueKey<int>(0),
424-
color: widget.iconTheme?.color,
425-
size: widget.iconTheme?.size,
426-
)
427-
: widget.child,
459+
child: Icon(
460+
widget.activeIcon,
461+
key: const ValueKey<int>(1),
462+
color: widget.iconTheme?.color,
463+
size: widget.iconTheme?.size,
464+
),
428465
),
429-
)
430-
: Transform.rotate(
431-
angle:
432-
widget.useRotationAnimation ? -pi * 1 / 2 : 0,
433-
child: widget.activeChild ??
434-
Container(
435-
decoration: BoxDecoration(
436-
shape: widget.gradientBoxShape,
437-
gradient: widget.gradient,
438-
),
439-
child: Center(
440-
child: Icon(
441-
widget.activeIcon,
442-
key: const ValueKey<int>(1),
443-
color: widget.iconTheme?.color,
444-
size: widget.iconTheme?.size,
445-
),
446-
),
447-
),
448-
)),
466+
),
467+
),
468+
),
449469
),
450470
);
451471

452472
var label = AnimatedSwitcher(
453473
duration: widget.animationDuration,
454-
transitionBuilder: widget.labelTransitionBuilder ??
474+
transitionBuilder:
475+
widget.labelTransitionBuilder ??
455476
(child, animation) => FadeTransition(
456-
opacity: animation,
457-
child: child,
458-
),
477+
opacity: animation,
478+
child: child,
479+
),
459480
child: (!_open || widget.activeLabel == null)
460481
? widget.label
461482
: widget.activeLabel,
462483
);
463484

464485
final backgroundColorTween = ColorTween(
465-
begin: widget.backgroundColor,
466-
end: widget.activeBackgroundColor ?? widget.backgroundColor);
486+
begin: widget.backgroundColor,
487+
end: widget.activeBackgroundColor ?? widget.backgroundColor,
488+
);
467489
final foregroundColorTween = ColorTween(
468-
begin: widget.foregroundColor,
469-
end: widget.activeForegroundColor ?? widget.foregroundColor);
490+
begin: widget.foregroundColor,
491+
end: widget.activeForegroundColor ?? widget.foregroundColor,
492+
);
470493

471494
var animatedFloatingButton = AnimatedBuilder(
472495
animation: _controller,
@@ -602,50 +625,55 @@ class _ChildrensOverlay extends StatelessWidget {
602625
fit: StackFit.loose,
603626
children: [
604627
Positioned(
605-
height: widget.maxHeight ??
628+
height:
629+
widget.maxHeight ??
606630
MediaQuery.of(context).size.height -
607631
(widget.childrenButtonSize.height * 3),
608632
child: CompositedTransformFollower(
609633
followerAnchor: widget.direction.isDown
610634
? widget.switchLabelPosition
611-
? Alignment.topLeft
612-
: Alignment.topRight
635+
? Alignment.topLeft
636+
: Alignment.topRight
613637
: widget.direction.isUp
614-
? widget.switchLabelPosition
615-
? Alignment.bottomLeft
616-
: Alignment.bottomRight
617-
: widget.direction.isLeft
618-
? Alignment.centerRight
619-
: widget.direction.isRight
620-
? Alignment.centerLeft
621-
: Alignment.center,
638+
? widget.switchLabelPosition
639+
? Alignment.bottomLeft
640+
: Alignment.bottomRight
641+
: widget.direction.isLeft
642+
? Alignment.centerRight
643+
: widget.direction.isRight
644+
? Alignment.centerLeft
645+
: Alignment.center,
622646
offset: widget.direction.isDown
623647
? Offset(
624648
(widget.switchLabelPosition ||
625649
dialKey.globalPaintBounds == null
626650
? 0
627651
: dialKey.globalPaintBounds!.size.width) +
628652
max(widget.childrenButtonSize.height - 56, 0) / 2,
629-
dialKey.globalPaintBounds!.size.height)
653+
dialKey.globalPaintBounds!.size.height,
654+
)
630655
: widget.direction.isUp
631-
? Offset(
632-
(widget.switchLabelPosition ||
633-
dialKey.globalPaintBounds == null
634-
? 0
635-
: dialKey.globalPaintBounds!.size.width) +
636-
max(widget.childrenButtonSize.width - 56, 0) / 2,
637-
0)
638-
: widget.direction.isLeft
639-
? Offset(
640-
-10.0,
641-
dialKey.globalPaintBounds == null
642-
? 0
643-
: dialKey.globalPaintBounds!.size.height / 2)
644-
: widget.direction.isRight &&
645-
dialKey.globalPaintBounds != null
646-
? Offset(dialKey.globalPaintBounds!.size.width + 12,
647-
dialKey.globalPaintBounds!.size.height / 2)
648-
: const Offset(-10.0, 0.0),
656+
? Offset(
657+
(widget.switchLabelPosition ||
658+
dialKey.globalPaintBounds == null
659+
? 0
660+
: dialKey.globalPaintBounds!.size.width) +
661+
max(widget.childrenButtonSize.width - 56, 0) / 2,
662+
0,
663+
)
664+
: widget.direction.isLeft
665+
? Offset(
666+
-10.0,
667+
dialKey.globalPaintBounds == null
668+
? 0
669+
: dialKey.globalPaintBounds!.size.height / 2,
670+
)
671+
: widget.direction.isRight && dialKey.globalPaintBounds != null
672+
? Offset(
673+
dialKey.globalPaintBounds!.size.width + 12,
674+
dialKey.globalPaintBounds!.size.height / 2,
675+
)
676+
: const Offset(-10.0, 0.0),
649677
link: layerLink,
650678
showWhenUnlinked: false,
651679
child: Material(
@@ -701,7 +729,8 @@ Widget _buildColumnOrRow(
701729
mainAxisAlignment: mainAxisAlignment ?? MainAxisAlignment.start,
702730
crossAxisAlignment: crossAxisAlignment ?? CrossAxisAlignment.center,
703731
children: children,
704-
))
732+
),
733+
)
705734
: Row(
706735
mainAxisSize: mainAxisSize ?? MainAxisSize.max,
707736
mainAxisAlignment: mainAxisAlignment ?? MainAxisAlignment.start,

‎pubspec.yaml‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ description: Flutter plugin to implement a beautiful and dynamic Material Design
33
version: 7.0.0
44
homepage: https://github.com/darioielardi/flutter_speed_dial/
55

6+
environment:
7+
sdk: '>=3.10.8 <4.0.0'
8+
flutter: ">=3.38.9 <4.0.0"
9+
resolution: workspace
10+
611
dependencies:
712
flutter:
813
sdk: flutter
914

1015
dev_dependencies:
1116
flutter_lints: 2.0.2
1217

13-
environment:
14-
sdk: '>=3.0.0 <4.0.0'
15-
flutter: ">=3.0.0 <4.0.0"
16-
1718
platforms:
1819
android:
1920
ios:

0 commit comments

Comments
 (0)