-
Notifications
You must be signed in to change notification settings - Fork 71
/
Copy pathcheck_mark_indicator.dart
145 lines (130 loc) · 3.81 KB
/
check_mark_indicator.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
143
144
145
import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class CheckMarkColors {
final Color content;
final Color background;
const CheckMarkColors({
required this.content,
required this.background,
});
}
class CheckMarkStyle {
final CheckMarkColors loading;
final CheckMarkColors success;
final CheckMarkColors error;
const CheckMarkStyle({
required this.loading,
required this.success,
required this.error,
});
static const defaultStyle = CheckMarkStyle(
loading: CheckMarkColors(
content: Colors.white,
background: Colors.blueAccent,
),
success: CheckMarkColors(
content: Colors.black,
background: Colors.greenAccent,
),
error: CheckMarkColors(
content: Colors.black,
background: Colors.redAccent,
),
);
}
class CheckMarkIndicator extends StatefulWidget {
final Widget child;
final CheckMarkStyle style;
final AsyncCallback onRefresh;
final IndicatorController? controller;
const CheckMarkIndicator({
super.key,
required this.child,
this.controller,
this.style = CheckMarkStyle.defaultStyle,
required this.onRefresh,
});
@override
State<CheckMarkIndicator> createState() => _CheckMarkIndicatorState();
}
class _CheckMarkIndicatorState extends State<CheckMarkIndicator>
with SingleTickerProviderStateMixin {
/// Whether to render check mark instead of spinner
bool _renderCompleteState = false;
ScrollDirection prevScrollDirection = ScrollDirection.idle;
bool _hasError = false;
Future<void> _handleRefresh() async {
try {
setState(() {
_hasError = false;
});
await widget.onRefresh();
} catch (_) {
setState(() {
_hasError = true;
});
rethrow;
}
}
@override
Widget build(BuildContext context) {
return CustomMaterialIndicator(
controller: widget.controller,
onRefresh: _handleRefresh,
durations: const RefreshIndicatorDurations(
completeDuration: Duration(seconds: 2),
),
onStateChanged: (change) {
/// set [_renderCompleteState] to true when controller.state become completed
if (change.didChange(to: IndicatorState.complete)) {
_renderCompleteState = true;
/// set [_renderCompleteState] to false when controller.state become idle
} else if (change.didChange(to: IndicatorState.idle)) {
_renderCompleteState = false;
}
},
indicatorBuilder: (
BuildContext context,
IndicatorController controller,
) {
final CheckMarkColors style;
if (_renderCompleteState) {
if (_hasError) {
style = widget.style.error;
} else {
style = widget.style.success;
}
} else {
style = widget.style.loading;
}
return AnimatedContainer(
duration: const Duration(milliseconds: 150),
alignment: Alignment.center,
decoration: BoxDecoration(
color: style.background,
shape: BoxShape.circle,
),
child: _renderCompleteState
? Icon(
_hasError ? Icons.close : Icons.check,
color: style.content,
)
: SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: style.content,
value: controller.isDragging || controller.isArmed
? controller.value.clamp(0.0, 1.0)
: null,
),
),
);
},
child: widget.child,
);
}
}