-
Notifications
You must be signed in to change notification settings - Fork 1
/
smiley_widget.dart
142 lines (127 loc) · 4.38 KB
/
smiley_widget.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import 'package:flutter/material.dart';
import 'eye_curve.dart';
import 'smiley_expression.dart';
const kDefaultSmileySize = 56.0;
class SmileyWidget extends StatefulWidget {
/// The expression of the smiley.
final SmileyExpression expression;
/// Selection state of the widget. By default it is `false`.
///
/// If `true` the widget will be drawn with a white border.
final bool isSelected;
/// Define if the widget is enabled or not. By default it is `true`.
///
/// If `false` the widget will be drawn with a `0.5` opacity.
final bool isEnabled;
/// Callback triggered when the widget is tapped on.
final VoidCallback? onTap;
/// Create a widget which draws an animated smiley face.
const SmileyWidget({
Key? key,
required this.expression,
this.isSelected = false,
this.isEnabled = true,
this.onTap,
}) : super(key: key);
@override
State<SmileyWidget> createState() => _SmileyWidgetState();
}
class _SmileyWidgetState extends State<SmileyWidget>
with SingleTickerProviderStateMixin {
late final _controller = AnimationController(
vsync: this,
duration: widget.expression.period,
);
late final Animation<double> _openingAnimation = CurvedAnimation(
parent: _controller,
curve: EyeCurve(
period: widget.expression.period,
blinking: const Duration(milliseconds: 500),
),
);
bool _isHovering = false;
bool get _isVeryHappy => widget.expression == SmileyExpression.veryHappy;
@override
void initState() {
super.initState();
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onExit: (_) => setState(() => _isHovering = false),
onEnter: (_) => setState(() => _isHovering = true),
child: AnimatedOpacity(
opacity: widget.isSelected || widget.isEnabled ? 1 : 0.5,
duration: const Duration(milliseconds: 200),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
height:
widget.isSelected ? kDefaultSmileySize * 1.2 : kDefaultSmileySize,
width:
widget.isSelected ? kDefaultSmileySize * 1.2 : kDefaultSmileySize,
alignment: Alignment.center,
child: GestureDetector(
onTap: widget.onTap,
child: Stack(
clipBehavior: Clip.none,
children: [
Container(
width: kDefaultSmileySize,
height: kDefaultSmileySize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.expression.color,
boxShadow: [
if (!widget.isSelected)
const BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.25),
offset: Offset(0, 4),
blurRadius: 4,
),
],
),
foregroundDecoration: const BoxDecoration(
backgroundBlendMode: BlendMode.overlay,
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(255, 255, 255, 1),
],
center: Alignment.bottomLeft,
radius: 1.8,
),
),
),
AnimatedPositioned(
height: kDefaultSmileySize,
width: kDefaultSmileySize,
top: widget.expression.topPos,
right: _isHovering || widget.isSelected
? kDefaultSmileySize / 2 -
widget.expression.rightPos *
(_isVeryHappy ? 1.4 : 1.8)
: widget.expression.rightPos,
duration: const Duration(milliseconds: 200),
child: CustomPaint(
painter: widget.expression.createPainter(_openingAnimation),
),
),
],
),
),
),
),
);
}
}