import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:lottie/lottie.dart'; import 'package:get/get.dart'; import 'package:nomo/config/theme/theme_extensions/theme_extension.dart'; import '../../components/ix_snackbar.dart'; import '../../data/models/api_exception.dart'; import '../../data/models/failure.dart'; import '../../../config/translations/strings_enum.dart'; import '../error_dialog.dart'; class SimpleLoadingDialog extends StatefulWidget { final Future Function() onRequest; // 异步请求函数 final VoidCallback? onSuccess; // 成功后的回调 final VoidCallback? onError; // 错误后的回调 final bool canCancel; // 是否可以取消 final VoidCallback? onCancel; // 取消后的回调 const SimpleLoadingDialog({ super.key, required this.onRequest, this.onSuccess, this.onError, this.canCancel = false, this.onCancel, }); static Future show({ required BuildContext context, required Future Function() onRequest, VoidCallback? onSuccess, VoidCallback? onError, bool canCancel = false, VoidCallback? onCancel, }) { return showDialog( context: context, barrierDismissible: false, builder: (context) => SimpleLoadingDialog( onRequest: onRequest, onSuccess: onSuccess, onError: onError, canCancel: canCancel, onCancel: onCancel, ), ); } @override State createState() => _SimpleLoadingDialogState(); } class _SimpleLoadingDialogState extends State with TickerProviderStateMixin { late final AnimationController _controller; bool _isCancelled = false; @override void initState() { super.initState(); _controller = AnimationController(vsync: this); _handleRequest(); } @override void dispose() { _controller.dispose(); super.dispose(); } Future _handleRequest() async { try { if (_isCancelled) return; await widget.onRequest(); if (_isCancelled) return; // 成功后直接关闭对话框并回调 if (mounted) { Navigator.of(context).pop(); widget.onSuccess?.call(); } } catch (e, s) { if (mounted) { Navigator.of(context).pop(); widget.onError?.call(); handleSnackBarError(e, s); } } } void handleSnackBarError(dynamic error, StackTrace stackTrace) { if (error is ApiException) { ErrorDialog.show(message: error.message); } else if (error is Failure) { ErrorDialog.show(message: error.message ?? Strings.unknownError.tr); } else if (error is DioException) { switch (error.type) { case DioExceptionType.connectionError: case DioExceptionType.connectionTimeout: case DioExceptionType.receiveTimeout: case DioExceptionType.sendTimeout: IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: Strings.unableToConnectNetwork.tr, ); break; default: IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: Strings.unableToConnectServer.tr, ); } } else { IXSnackBar.showIXErrorSnackBar( title: Strings.error.tr, message: error.toString(), ); } } void _handleCancel() { setState(() { _isCancelled = true; }); Navigator.of(context).pop(); widget.onCancel?.call(); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async => widget.canCancel, child: Dialog( backgroundColor: Colors.transparent, child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 32.w, height: 32.w, child: Lottie.asset( 'assets/lottie/loading.json', controller: _controller, frameRate: FrameRate.max, onLoaded: (composition) { _controller.duration = const Duration(milliseconds: 3000); _controller.addListener(() { if (_controller.value >= 0.5) { _controller.value = 0.0; _controller.forward(); } }); _controller.forward(); }, ), ), if (widget.canCancel) ...[ 16.verticalSpaceFromWidth, GestureDetector( onTap: _handleCancel, child: Container( padding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 8.h, ), decoration: BoxDecoration( color: Get.reactiveTheme.scaffoldBackgroundColor, borderRadius: BorderRadius.circular(8.r), border: Border.all( color: Get.reactiveTheme.scaffoldBackgroundColor, width: 1.w, ), ), child: Text( Strings.cancel.tr, style: TextStyle( color: Get.reactiveTheme.textTheme.bodyLarge!.color, fontSize: 12.sp, fontWeight: FontWeight.w500, ), ), ), ), ], ], ), ), ); } }