Skip to content

Commit 7968453

Browse files
authored
✨ Add the loading indicator when saving (#140)
1 parent 66426df commit 7968453

File tree

6 files changed

+168
-24
lines changed

6 files changed

+168
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ that can be found in the LICENSE file. -->
44

55
# Changelog
66

7+
## 3.6.3
8+
9+
### Improvements
10+
11+
- Add the loading indicator when saving. (#140)
12+
713
## 3.6.2
814

915
### Improvements

example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: wechat_camera_picker_demo
22
description: A new Flutter project.
3-
version: 3.6.2+18
3+
version: 3.6.3+19
44
publish_to: none
55

66
environment:

lib/src/delegates/camera_picker_text_delegate.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ class CameraPickerTextDelegate {
5757
/// 资源加载失败时的字段
5858
String get loadFailed => '加载失败';
5959

60+
/// Default loading string for the dialog.
61+
/// 加载中弹窗的默认文字
62+
String get loading => '加载中…';
63+
64+
/// Saving string for the dialog.
65+
/// 保存中弹窗的默认文字
66+
String get saving => '保存中…';
67+
6068
/// Semantics fields.
6169
///
6270
/// Fields below are only for semantics usage. For customizable these fields,
@@ -141,6 +149,12 @@ class EnglishCameraPickerTextDelegate extends CameraPickerTextDelegate {
141149
@override
142150
String get loadFailed => 'Load failed';
143151

152+
@override
153+
String get loading => 'Loading...';
154+
155+
@override
156+
String get saving => 'Saving...';
157+
144158
@override
145159
String get sActionManuallyFocusHint => 'manually focus';
146160

lib/src/states/camera_picker_viewer_state.dart

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:async';
66
import 'dart:io';
7+
import 'dart:math' as math;
78

89
import 'package:flutter/material.dart';
910
import 'package:flutter/semantics.dart';
@@ -127,7 +128,9 @@ class CameraPickerViewerState extends State<CameraPickerViewer> {
127128
if (isSavingEntity) {
128129
return;
129130
}
130-
isSavingEntity = true;
131+
setState(() {
132+
isSavingEntity = true;
133+
});
131134
final CameraPickerViewType viewType = widget.viewType;
132135
if (widget.pickerConfig.onEntitySaving != null) {
133136
try {
@@ -138,8 +141,10 @@ class CameraPickerViewerState extends State<CameraPickerViewer> {
138141
);
139142
} catch (e, s) {
140143
handleErrorWithHandler(e, widget.pickerConfig.onError, s: s);
141-
} finally {
142-
isSavingEntity = false;
144+
}
145+
isSavingEntity = false;
146+
if (mounted) {
147+
setState(() {});
143148
}
144149
return;
145150
}
@@ -194,6 +199,9 @@ class CameraPickerViewerState extends State<CameraPickerViewer> {
194199
padding: const EdgeInsets.all(10),
195200
child: IconButton(
196201
onPressed: () {
202+
if (isSavingEntity) {
203+
return;
204+
}
197205
if (previewFile.existsSync()) {
198206
previewFile.delete();
199207
}
@@ -209,7 +217,10 @@ class CameraPickerViewerState extends State<CameraPickerViewer> {
209217
color: Colors.white,
210218
shape: BoxShape.circle,
211219
),
212-
child: const Icon(Icons.keyboard_return_rounded, color: Colors.black,),
220+
child: const Icon(
221+
Icons.keyboard_return_rounded,
222+
color: Colors.black,
223+
),
213224
),
214225
),
215226
),
@@ -329,6 +340,16 @@ class CameraPickerViewerState extends State<CameraPickerViewer> {
329340
);
330341
}
331342

343+
Widget buildLoading(BuildContext context) {
344+
return IgnorePointer(
345+
child: AnimatedOpacity(
346+
duration: kThemeAnimationDuration,
347+
opacity: isSavingEntity ? 1 : 0,
348+
child: _WechatLoading(tip: Constants.textDelegate.saving),
349+
),
350+
);
351+
}
352+
332353
@override
333354
Widget build(BuildContext context) {
334355
if (hasErrorWhenInitializing) {
@@ -349,8 +370,115 @@ class CameraPickerViewerState extends State<CameraPickerViewer> {
349370
children: <Widget>[
350371
buildPreview(context),
351372
buildForeground(context),
373+
buildLoading(context),
352374
],
353375
),
354376
);
355377
}
356378
}
379+
380+
class _WechatLoading extends StatefulWidget {
381+
const _WechatLoading({Key? key, required this.tip}) : super(key: key);
382+
383+
final String tip;
384+
385+
@override
386+
State<_WechatLoading> createState() => _WechatLoadingState();
387+
}
388+
389+
class _WechatLoadingState extends State<_WechatLoading>
390+
with SingleTickerProviderStateMixin {
391+
late final AnimationController _controller = AnimationController(
392+
duration: const Duration(seconds: 2),
393+
vsync: this,
394+
);
395+
396+
@override
397+
void initState() {
398+
super.initState();
399+
_controller.repeat();
400+
}
401+
402+
@override
403+
void dispose() {
404+
_controller.dispose();
405+
super.dispose();
406+
}
407+
408+
Widget _buildContent(BuildContext context, double minWidth) {
409+
return Column(
410+
mainAxisSize: MainAxisSize.min,
411+
children: <Widget>[
412+
SizedBox.fromSize(
413+
size: Size.square(minWidth / 3),
414+
child: AnimatedBuilder(
415+
animation: _controller,
416+
builder: (_, Widget? child) => Transform.rotate(
417+
angle: math.pi * 2 * _controller.value,
418+
child: child,
419+
),
420+
child: CustomPaint(
421+
painter: _LoadingPainter(
422+
Theme.of(context).textTheme.bodyMedium?.color,
423+
),
424+
),
425+
),
426+
),
427+
SizedBox(height: minWidth / 10),
428+
Text(
429+
widget.tip,
430+
style: const TextStyle(fontSize: 14),
431+
textScaleFactor: 1,
432+
),
433+
],
434+
);
435+
}
436+
437+
@override
438+
Widget build(BuildContext context) {
439+
final double minWidth = MediaQuery.of(context).size.shortestSide / 3;
440+
return Container(
441+
color: Colors.black38,
442+
alignment: Alignment.center,
443+
child: RepaintBoundary(
444+
child: Container(
445+
constraints: BoxConstraints(minWidth: minWidth),
446+
padding: EdgeInsets.all(minWidth / 5),
447+
decoration: BoxDecoration(
448+
borderRadius: BorderRadius.circular(10),
449+
color: Theme.of(context).canvasColor,
450+
),
451+
child: _buildContent(context, minWidth),
452+
),
453+
),
454+
);
455+
}
456+
}
457+
458+
class _LoadingPainter extends CustomPainter {
459+
const _LoadingPainter(this.activeColor);
460+
461+
final Color? activeColor;
462+
463+
@override
464+
void paint(Canvas canvas, Size size) {
465+
final Color color = activeColor ?? Colors.white;
466+
final Offset center = Offset(size.width / 2, size.height / 2);
467+
final Rect rect = Rect.fromCenter(
468+
center: center,
469+
width: size.width,
470+
height: size.height,
471+
);
472+
final Paint paint = Paint()
473+
..style = PaintingStyle.stroke
474+
..strokeCap = StrokeCap.round
475+
..strokeWidth = 4
476+
..shader = SweepGradient(
477+
colors: <Color>[color.withOpacity(0), color],
478+
).createShader(rect);
479+
canvas.drawArc(rect, 0.1, math.pi * 2 * 0.9, false, paint);
480+
}
481+
482+
@override
483+
bool shouldRepaint(_LoadingPainter oldDelegate) => false;
484+
}

lib/src/widgets/camera_progress_button.dart

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,17 @@ class _CircleProgressState extends State<CameraProgressButton>
5656
Widget build(BuildContext context) {
5757
final Size size = Size.square(widget.outerRadius * 2);
5858
return Center(
59-
child: AnimatedBuilder(
60-
animation: progressController,
61-
builder: (_, __) => CustomPaint(
62-
key: paintKey,
63-
size: size,
64-
painter: CameraProgressButtonPainter(
65-
progress: progressController.value,
66-
ringsWidth: widget.ringsWidth,
67-
ringsColor: widget.ringsColor,
59+
child: RepaintBoundary(
60+
child: AnimatedBuilder(
61+
animation: progressController,
62+
builder: (_, __) => CustomPaint(
63+
key: paintKey,
64+
size: size,
65+
painter: CameraProgressButtonPainter(
66+
progress: progressController.value,
67+
ringsWidth: widget.ringsWidth,
68+
ringsColor: widget.ringsColor,
69+
),
6870
),
6971
),
7072
),
@@ -88,16 +90,14 @@ class CameraProgressButtonPainter extends CustomPainter {
8890
final double center = size.width / 2;
8991
final Offset offsetCenter = Offset(center, center);
9092
final double drawRadius = size.width / 2 - ringsWidth;
91-
final double angle = 360.0 * progress;
92-
final double radians = angle.toRad;
9393

9494
final double outerRadius = center;
9595
final double innerRadius = center - ringsWidth * 2;
9696

9797
final double progressWidth = outerRadius - innerRadius;
9898
canvas.save();
9999
canvas.translate(0.0, size.width);
100-
canvas.rotate(-90.0.toRad);
100+
canvas.rotate(-math.pi / 2);
101101
final Rect arcRect = Rect.fromCircle(
102102
center: offsetCenter,
103103
radius: drawRadius,
@@ -107,14 +107,10 @@ class CameraProgressButtonPainter extends CustomPainter {
107107
..style = PaintingStyle.stroke
108108
..strokeWidth = progressWidth;
109109
canvas
110-
..drawArc(arcRect, 0, radians, false, progressPaint)
110+
..drawArc(arcRect, 0, math.pi * 2 * progress, false, progressPaint)
111111
..restore();
112112
}
113113

114114
@override
115-
bool shouldRepaint(CustomPainter oldDelegate) => true;
116-
}
117-
118-
extension _MathExtension on double {
119-
double get toRad => this * (math.pi / 180.0);
115+
bool shouldRepaint(CameraProgressButtonPainter oldDelegate) => true;
120116
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: wechat_camera_picker
22
description: A camera picker based on WeChat's UI which is a separate runnable extension to wechat_assets_picker.
33
repository: https://github.com/fluttercandies/flutter_wechat_camera_picker
4-
version: 3.6.2
4+
version: 3.6.3
55

66
environment:
77
sdk: ">=2.14.0 <3.0.0"

0 commit comments

Comments
 (0)