Line data Source code
1 : import 'package:flutter/material.dart';
2 : import 'package:flutter/services.dart';
3 : import 'package:pal/src/theme.dart';
4 : import 'package:pal/src/ui/shared/widgets/circle_image.dart';
5 :
6 : class BubbleOverlayButton extends StatefulWidget {
7 : final ValueNotifier<bool> visibility;
8 :
9 : final double width, height;
10 :
11 : final Size screenSize;
12 :
13 : final Function onTapCallback;
14 :
15 6 : const BubbleOverlayButton({
16 : Key key,
17 : @required this.screenSize,
18 : @required this.visibility,
19 : this.height = 64.0,
20 : this.width = 64.0,
21 : this.onTapCallback,
22 6 : }) : super(key: key);
23 :
24 6 : @override
25 6 : _BubbleOverlayButtonState createState() => _BubbleOverlayButtonState();
26 : }
27 :
28 : class _BubbleOverlayButtonState extends State<BubbleOverlayButton>
29 : with SingleTickerProviderStateMixin {
30 : Offset _position;
31 : AnimationController _animationController;
32 : double _scale;
33 : Duration _duration = Duration(milliseconds: 100);
34 :
35 6 : @override
36 : void initState() {
37 6 : super.initState();
38 12 : _position = Offset(
39 48 : widget.screenSize.width / 2 - (widget.width / 2),
40 42 : widget.screenSize.height - (widget.height * 2),
41 : );
42 24 : widget.visibility.addListener(_onVisibilityChange);
43 12 : _animationController = AnimationController(
44 : vsync: this,
45 6 : duration: _duration,
46 : lowerBound: 0.0,
47 : upperBound: 0.1,
48 7 : )..addListener(() {
49 2 : setState(() {});
50 : });
51 6 : super.initState();
52 : }
53 :
54 1 : _onVisibilityChange() {
55 2 : setState(() {});
56 : }
57 :
58 6 : @override
59 : void dispose() {
60 24 : widget.visibility.removeListener(_onVisibilityChange);
61 12 : _animationController?.dispose();
62 :
63 6 : super.dispose();
64 : }
65 :
66 6 : @override
67 : Widget build(BuildContext context) {
68 24 : _scale = 1 - _animationController.value;
69 6 : if (_position != null) {
70 6 : return Visibility(
71 18 : visible: widget.visibility.value,
72 6 : child: Positioned(
73 12 : left: _position.dx,
74 12 : top: _position.dy,
75 6 : child: GestureDetector(
76 0 : onPanUpdate: (details) {
77 0 : setState(() {
78 0 : _position = details.globalPosition -
79 0 : Offset(
80 0 : widget.width / 2,
81 0 : widget.height / 2,
82 : );
83 : });
84 : },
85 6 : child: _buildBubble(),
86 : ),
87 : ),
88 : );
89 : } else {
90 0 : return Container();
91 : }
92 : }
93 :
94 1 : _onTapDown(TapDownDetails details) {
95 2 : _animationController.forward();
96 : }
97 :
98 1 : _onTapUp(TapUpDetails details) {
99 3 : Future.delayed(_duration, () {
100 2 : _animationController.reverse();
101 : });
102 :
103 1 : HapticFeedback.selectionClick();
104 2 : if (widget.onTapCallback != null) {
105 3 : widget.onTapCallback();
106 : }
107 : }
108 :
109 0 : _onTapCancel() {
110 0 : _animationController.reverse();
111 : }
112 :
113 6 : Widget _buildBubble() {
114 6 : return SizedBox(
115 12 : height: widget.height,
116 12 : width: widget.width,
117 6 : child: GestureDetector(
118 6 : onTapDown: _onTapDown,
119 6 : onTapUp: _onTapUp,
120 6 : onTapCancel: _onTapCancel,
121 6 : child: Transform.scale(
122 6 : scale: _scale,
123 6 : child: CircleImageButton(
124 18 : backgroundColor: PalTheme.of(context).floatingBubbleBackgroundColor,
125 18 : radius: widget.width / 2,
126 6 : shadow: BoxShadow(
127 6 : color: Colors.black.withOpacity(0.15),
128 : spreadRadius: 4,
129 : blurRadius: 8,
130 6 : offset: Offset(0, 3),
131 : ),
132 : image: 'packages/pal/assets/images/logo.png',
133 : ),
134 : ),
135 : ),
136 : );
137 : }
138 : }
|