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 '../../widgets/submit_btn.dart'; import '../error_dialog.dart'; enum LoadingState { loading, success } class LoadingDialog extends StatefulWidget { final String? loadingText; final String? successText; final Future Function() onRequest; // 异步请求函数 final VoidCallback? onSuccess; // 成功后的回调 final VoidCallback? onError; // 错误后的回调 final bool canCancel; // 是否可以取消 final VoidCallback? onCancel; // 取消后的回调 const LoadingDialog({ super.key, this.loadingText = 'Processing...', this.successText = 'Success', required this.onRequest, this.onSuccess, this.onError, this.canCancel = false, // 默认为false this.onCancel, }); static Future show({ required BuildContext context, required Future Function() onRequest, String? loadingText, String? successText, VoidCallback? onSuccess, VoidCallback? onError, bool canCancel = false, VoidCallback? onCancel, }) { return showDialog( context: context, barrierDismissible: false, builder: (context) => LoadingDialog( loadingText: loadingText ?? Strings.processing.tr, successText: successText ?? Strings.success.tr, onRequest: onRequest, onSuccess: onSuccess, onError: onError, canCancel: canCancel, onCancel: onCancel, ), ); } @override State createState() => _LoadingDialogState(); } class _LoadingDialogState extends State with TickerProviderStateMixin { LoadingState _state = LoadingState.loading; late final AnimationController _controller; final Duration _loadingDuration = const Duration(milliseconds: 3000); final Duration _successDuration = const Duration(milliseconds: 1500); 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; setState(() { _state = LoadingState.success; _controller.duration = _successDuration; _controller.forward(); }); // 成功后延迟关闭对话框 await Future.delayed(_successDuration); 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, // 根据canCancel决定是否可以返回 child: Dialog( backgroundColor: Get.reactiveTheme.scaffoldBackgroundColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // side: BorderSide(color: Palette.white.withOpacity(0.12)), ), child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Get.reactiveTheme.scaffoldBackgroundColor, borderRadius: BorderRadius.circular(16), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Stack( alignment: Alignment.center, children: [ SizedBox( width: 46.w, height: 46.w, child: Lottie.asset( 'assets/lottie/loading.json', controller: _controller, frameRate: FrameRate.max, onLoaded: (composition) { _controller.duration = _loadingDuration; _controller.addListener(() { if (_state == LoadingState.loading && _controller.value >= 0.5) { _controller.value = 0.0; _controller.forward(); } }); _controller.forward(); }, ), ), ], ), 16.verticalSpaceFromWidth, Text( (_state == LoadingState.loading ? (widget.loadingText ?? Strings.processing.tr) : (widget.successText ?? Strings.success.tr)), style: TextStyle( color: Get.reactiveTheme.textTheme.bodyLarge!.color, fontSize: 12.sp, fontWeight: FontWeight.w500, ), ), if (widget.canCancel) Column( children: [ 16.verticalSpaceFromWidth, SubmitButton( text: Strings.cancel.tr, onPressed: _handleCancel, ), ], ), ], ), ), ), ); } }