// custom_alert_dialog.dart import 'dart:async'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/main.dart'; // 导入全局 navigatorKey /// 对话框模式 enum DialogMode { text, input, inputWithCode } class CustomAlertDialog extends StatefulWidget { final String title; final String content; final String hintText; final String cancelText; final String confirmText; final VoidCallback? onCancel; final VoidCallback? onConfirm; final ValueChanged? onInputConfirm; final DialogMode mode; final bool force; /// 新增:获取验证码回调,若提供则在用户点击获取验证码时调用。 /// 回调返回 true 表示获取成功(开始倒计时),返回 false 表示获取失败(不开始倒计时)。 final Future Function()? onGetCode; const CustomAlertDialog({ Key? key, required this.title, this.content = '', this.hintText = '', this.cancelText = '取消', this.confirmText = '确定', this.onCancel, this.onConfirm, this.onInputConfirm, this.mode = DialogMode.text, this.force = false, this.onGetCode, }) : super(key: key); // ------------------ 静态快捷方法 ------------------ static Future showConfirm( BuildContext context, { required String title, String content = '', String cancelText = '取消', String confirmText = '确定', bool barrierDismissible = true, VoidCallback? onConfirm, bool force = false, }) async { final result = await showDialog( context: context, barrierDismissible: force ? false : barrierDismissible, builder: (_) => PopScope( canPop: false, child: CustomAlertDialog( title: title, content: content, cancelText: cancelText, confirmText: confirmText, onConfirm: onConfirm, force: force, ), ), ); return result == true; } static Future showAlert( BuildContext context, { required String title, String content = '', String confirmText = '确定', bool barrierDismissible = true, VoidCallback? onConfirm, bool force = false, }) async { await showDialog( context: context, barrierDismissible: force ? false : barrierDismissible, builder: (_) => CustomAlertDialog( title: title, content: content, cancelText: '', confirmText: confirmText, onConfirm: onConfirm, force: force, ), ); } static Future showInput( BuildContext context, { required String title, String hintText = '', String cancelText = '取消', String confirmText = '确定', bool barrierDismissible = true, bool force = false, }) async { final result = await showDialog( context: context, barrierDismissible: force ? false : barrierDismissible, builder: (_) => CustomAlertDialog( title: title, hintText: hintText, cancelText: cancelText, confirmText: confirmText, mode: DialogMode.input, force: force, ), ); // 取消/点遮罩会得到 null;确认会得到 String(可能为空串) return result; } /// 新增:快捷方法——输入框 + 获取验证码 static Future showInputWithCode( BuildContext context, { required String title, String hintText = '', String cancelText = '取消', String confirmText = '确定', bool barrierDismissible = true, bool force = false, Future Function()? onGetCode, ValueChanged? onConfirm, // <-- 这里改为带参数的回调 }) async { final result = await showDialog( context: context, barrierDismissible: force ? false : barrierDismissible, builder: (_) => CustomAlertDialog( title: title, hintText: hintText, cancelText: cancelText, confirmText: confirmText, mode: DialogMode.inputWithCode, force: force, onGetCode: onGetCode, onInputConfirm: onConfirm, // 把回调传给内部使用的 onInputConfirm ), ); return result; } @override _CustomAlertDialogState createState() => _CustomAlertDialogState(); } class _CustomAlertDialogState extends State { late TextEditingController _controller; bool _isClosing = false; // 验证码计时器相关 Timer? _timer; int _seconds = 0; static const int _defaultCountdown = 60; @override void initState() { super.initState(); _controller = TextEditingController(); } @override void dispose() { _timer?.cancel(); _controller.dispose(); super.dispose(); } bool get hasCancel => !widget.force && widget.cancelText.isNotEmpty; void _closeDialog([dynamic result]) { if (_isClosing) return; _isClosing = true; // 优先使用当前上下文导航 if (mounted) { Navigator.of(context).pop(result); } else { // 后备方案:使用全局导航键 navigatorKey.currentState?.pop(result); } } void _startCountdown([int seconds = _defaultCountdown]) { _timer?.cancel(); setState(() { _seconds = seconds; }); _timer = Timer.periodic(const Duration(seconds: 1), (t) { if (!mounted) { t.cancel(); return; } if (_seconds <= 1) { t.cancel(); setState(() { _seconds = 0; }); } else { setState(() { _seconds -= 1; }); } }); } Future _onGetCodePressed() async { if (_seconds > 0) return; // 正在倒计时,忽略 // 如果外部提供了回调,等待其执行并根据返回值决定是否开始倒计时 if (widget.onGetCode != null) { await widget.onGetCode!(); _startCountdown(); } else { // 未提供回调:默认直接开始倒计时(方便调试) _startCountdown(); } } @override Widget build(BuildContext context) { return PopScope( canPop: !widget.force, child: Dialog( backgroundColor: Colors.transparent, child: Container( constraints: const BoxConstraints(minWidth: 280), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Text( widget.title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), const SizedBox(height: 16), if (widget.mode == DialogMode.text) Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Text( widget.content, style: const TextStyle(fontSize: 16, color: Colors.black54), textAlign: TextAlign.center, ), ) else if (widget.mode == DialogMode.input) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: TextField( controller: _controller, autofocus: true, decoration: InputDecoration( hintText: widget.hintText, border: const OutlineInputBorder(), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.blue, width: 1), borderRadius: BorderRadius.circular(4), ), isDense: true, contentPadding: const EdgeInsets.symmetric( vertical: 10, horizontal: 10, ), ), ), ) else // DialogMode.inputWithCode Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( children: [ Expanded( child: TextField( controller: _controller, autofocus: true, decoration: InputDecoration( hintText: widget.hintText, border: const OutlineInputBorder(), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.blue, width: 1, ), borderRadius: BorderRadius.circular(4), ), isDense: true, contentPadding: const EdgeInsets.symmetric( vertical: 10, horizontal: 10, ), ), ), ), const SizedBox(width: 8), SizedBox( height: 44, width: 100, child: CustomButton( text: _seconds > 0 ? '$_seconds s' : '发送验证码', onPressed: _seconds > 0 ? null : _onGetCodePressed, ), ), ], ), ), const SizedBox(height: 20), const Divider(height: 1), hasCancel ? _buildDoubleButtons(context) : _buildSingleButton(context), ], ), ), ), ); } Widget _buildDoubleButtons(BuildContext context) { return Row( children: [ Expanded( child: InkWell( onTap: () { widget.onCancel?.call(); _closeDialog(widget.mode == DialogMode.text ? false : null); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 12), alignment: Alignment.center, child: Text( widget.cancelText, style: const TextStyle( fontWeight: FontWeight.w500, color: Colors.black87, fontSize: 18, ), ), ), ), ), Container(width: 1, height: 48, color: Colors.grey[300]), Expanded( child: InkWell( onTap: () { if (widget.mode == DialogMode.text) { widget.onConfirm?.call(); _closeDialog(true); } else { final value = _controller.text.trim(); widget.onInputConfirm?.call(value); _closeDialog(value); } }, child: Container( padding: const EdgeInsets.symmetric(vertical: 12), alignment: Alignment.center, child: Text( widget.confirmText, style: const TextStyle( color: Color(0xFF1C61FF), fontWeight: FontWeight.w500, fontSize: 18, ), ), ), ), ), ], ); } Widget _buildSingleButton(BuildContext context) { return InkWell( onTap: () { if (widget.mode == DialogMode.text) { widget.onConfirm?.call(); _closeDialog(true); } else { final value = _controller.text.trim(); widget.onInputConfirm?.call(value); _closeDialog(value); } }, child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 14), alignment: Alignment.center, child: Text( widget.confirmText, style: const TextStyle( color: Color(0xFF1C61FF), fontWeight: FontWeight.w500, fontSize: 18, ), ), ), ); } }