QinGang_interested/lib/customWidget/custom_alert_dialog.dart

420 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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<String>? onInputConfirm;
final DialogMode mode;
final bool force;
/// 新增:获取验证码回调,若提供则在用户点击获取验证码时调用。
/// 回调返回 true 表示获取成功(开始倒计时),返回 false 表示获取失败(不开始倒计时)。
final Future<bool> 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<bool> 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<bool>(
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<void> showAlert(
BuildContext context, {
required String title,
String content = '',
String confirmText = '确定',
bool barrierDismissible = true,
VoidCallback? onConfirm,
bool force = false,
}) async {
await showDialog<void>(
context: context,
barrierDismissible: force ? false : barrierDismissible,
builder:
(_) => CustomAlertDialog(
title: title,
content: content,
cancelText: '',
confirmText: confirmText,
onConfirm: onConfirm,
force: force,
),
);
}
static Future<String?> showInput(
BuildContext context, {
required String title,
String hintText = '',
String cancelText = '取消',
String confirmText = '确定',
bool barrierDismissible = true,
bool force = false,
}) async {
final result = await showDialog<String?>(
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<String?> showInputWithCode(
BuildContext context, {
required String title,
String hintText = '',
String cancelText = '取消',
String confirmText = '确定',
bool barrierDismissible = true,
bool force = false,
Future<bool> Function()? onGetCode,
ValueChanged<String>? onConfirm, // <-- 这里改为带参数的回调
}) async {
final result = await showDialog<String?>(
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<CustomAlertDialog> {
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<void> _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,
),
),
),
);
}
}