simple_loading_dialog.dart 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import 'package:dio/dio.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. import 'package:lottie/lottie.dart';
  5. import 'package:get/get.dart';
  6. import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
  7. import '../../components/ix_snackbar.dart';
  8. import '../../data/models/api_exception.dart';
  9. import '../../data/models/failure.dart';
  10. import '../../../config/translations/strings_enum.dart';
  11. import '../error_dialog.dart';
  12. class SimpleLoadingDialog extends StatefulWidget {
  13. final Future<void> Function() onRequest; // 异步请求函数
  14. final VoidCallback? onSuccess; // 成功后的回调
  15. final VoidCallback? onError; // 错误后的回调
  16. final bool canCancel; // 是否可以取消
  17. final VoidCallback? onCancel; // 取消后的回调
  18. const SimpleLoadingDialog({
  19. super.key,
  20. required this.onRequest,
  21. this.onSuccess,
  22. this.onError,
  23. this.canCancel = false,
  24. this.onCancel,
  25. });
  26. static Future<void> show({
  27. required BuildContext context,
  28. required Future<void> Function() onRequest,
  29. VoidCallback? onSuccess,
  30. VoidCallback? onError,
  31. bool canCancel = false,
  32. VoidCallback? onCancel,
  33. }) {
  34. return showDialog(
  35. context: context,
  36. barrierDismissible: false,
  37. builder: (context) => SimpleLoadingDialog(
  38. onRequest: onRequest,
  39. onSuccess: onSuccess,
  40. onError: onError,
  41. canCancel: canCancel,
  42. onCancel: onCancel,
  43. ),
  44. );
  45. }
  46. @override
  47. State<SimpleLoadingDialog> createState() => _SimpleLoadingDialogState();
  48. }
  49. class _SimpleLoadingDialogState extends State<SimpleLoadingDialog>
  50. with TickerProviderStateMixin {
  51. late final AnimationController _controller;
  52. bool _isCancelled = false;
  53. @override
  54. void initState() {
  55. super.initState();
  56. _controller = AnimationController(vsync: this);
  57. _handleRequest();
  58. }
  59. @override
  60. void dispose() {
  61. _controller.dispose();
  62. super.dispose();
  63. }
  64. Future<void> _handleRequest() async {
  65. try {
  66. if (_isCancelled) return;
  67. await widget.onRequest();
  68. if (_isCancelled) return;
  69. // 成功后直接关闭对话框并回调
  70. if (mounted) {
  71. Navigator.of(context).pop();
  72. widget.onSuccess?.call();
  73. }
  74. } catch (e, s) {
  75. if (mounted) {
  76. Navigator.of(context).pop();
  77. widget.onError?.call();
  78. handleSnackBarError(e, s);
  79. }
  80. }
  81. }
  82. void handleSnackBarError(dynamic error, StackTrace stackTrace) {
  83. if (error is ApiException) {
  84. ErrorDialog.show(message: error.message);
  85. } else if (error is Failure) {
  86. ErrorDialog.show(message: error.message ?? Strings.unknownError.tr);
  87. } else if (error is DioException) {
  88. switch (error.type) {
  89. case DioExceptionType.connectionError:
  90. case DioExceptionType.connectionTimeout:
  91. case DioExceptionType.receiveTimeout:
  92. case DioExceptionType.sendTimeout:
  93. IXSnackBar.showIXErrorSnackBar(
  94. title: Strings.error.tr,
  95. message: Strings.unableToConnectNetwork.tr,
  96. );
  97. break;
  98. default:
  99. IXSnackBar.showIXErrorSnackBar(
  100. title: Strings.error.tr,
  101. message: Strings.unableToConnectServer.tr,
  102. );
  103. }
  104. } else {
  105. IXSnackBar.showIXErrorSnackBar(
  106. title: Strings.error.tr,
  107. message: error.toString(),
  108. );
  109. }
  110. }
  111. void _handleCancel() {
  112. setState(() {
  113. _isCancelled = true;
  114. });
  115. Navigator.of(context).pop();
  116. widget.onCancel?.call();
  117. }
  118. @override
  119. Widget build(BuildContext context) {
  120. return WillPopScope(
  121. onWillPop: () async => widget.canCancel,
  122. child: Dialog(
  123. backgroundColor: Colors.transparent,
  124. child: Column(
  125. mainAxisSize: MainAxisSize.min,
  126. children: [
  127. SizedBox(
  128. width: 32.w,
  129. height: 32.w,
  130. child: Lottie.asset(
  131. 'assets/lottie/loading.json',
  132. controller: _controller,
  133. frameRate: FrameRate.max,
  134. onLoaded: (composition) {
  135. _controller.duration = const Duration(milliseconds: 3000);
  136. _controller.addListener(() {
  137. if (_controller.value >= 0.5) {
  138. _controller.value = 0.0;
  139. _controller.forward();
  140. }
  141. });
  142. _controller.forward();
  143. },
  144. ),
  145. ),
  146. if (widget.canCancel) ...[
  147. 16.verticalSpaceFromWidth,
  148. GestureDetector(
  149. onTap: _handleCancel,
  150. child: Container(
  151. padding: EdgeInsets.symmetric(
  152. horizontal: 16.w,
  153. vertical: 8.h,
  154. ),
  155. decoration: BoxDecoration(
  156. color: Get.reactiveTheme.scaffoldBackgroundColor,
  157. borderRadius: BorderRadius.circular(8.r),
  158. border: Border.all(
  159. color: Get.reactiveTheme.scaffoldBackgroundColor,
  160. width: 1.w,
  161. ),
  162. ),
  163. child: Text(
  164. Strings.cancel.tr,
  165. style: TextStyle(
  166. color: Get.reactiveTheme.textTheme.bodyLarge!.color,
  167. fontSize: 12.sp,
  168. fontWeight: FontWeight.w500,
  169. ),
  170. ),
  171. ),
  172. ),
  173. ],
  174. ],
  175. ),
  176. ),
  177. );
  178. }
  179. }