flutter_integrated_whb/lib/customWidget/custom_alert_dialog.dart

290 lines
8.5 KiB
Dart
Raw Normal View History

2025-07-16 08:37:08 +08:00
import 'package:flutter/material.dart';
2025-07-28 14:22:07 +08:00
/// 对话框模式
enum DialogMode { text, input }
class CustomAlertDialog extends StatefulWidget {
2025-07-16 08:37:08 +08:00
final String title;
2025-07-28 14:22:07 +08:00
final String content; // 文字模式下显示
final String hintText; // 输入模式下提示
2025-07-16 08:37:08 +08:00
final String cancelText;
final String confirmText;
final VoidCallback? onCancel;
2025-07-28 14:22:07 +08:00
final VoidCallback? onConfirm; // 文字模式回调
final ValueChanged<String>? onInputConfirm; // 输入模式回调
final DialogMode mode; // 对话框模式
2025-07-16 08:37:08 +08:00
const CustomAlertDialog({
Key? key,
required this.title,
2025-07-28 14:22:07 +08:00
this.content = '',
this.hintText = '',
this.cancelText = '取消',
this.confirmText = '确定',
2025-07-16 08:37:08 +08:00
this.onCancel,
this.onConfirm,
2025-07-28 14:22:07 +08:00
this.onInputConfirm,
this.mode = DialogMode.text,
2025-07-16 08:37:08 +08:00
}) : super(key: key);
@override
2025-07-28 14:22:07 +08:00
_CustomAlertDialogState createState() => _CustomAlertDialogState();
// ------------------ 快捷静态方法 ------------------
/// 带“取消/确定”的确认弹窗
/// 返回 true = 确定false = 取消或关闭
static Future<bool> showConfirm(
BuildContext context, {
required String title,
String content = '',
String cancelText = '取消',
String confirmText = '确定',
bool barrierDismissible = true,
2025-08-19 11:06:16 +08:00
final VoidCallback? onConfirm,
}) async {
final result = await showDialog<bool>(
context: context,
barrierDismissible: barrierDismissible,
builder: (_) {
return CustomAlertDialog(
title: title,
content: content,
cancelText: cancelText,
confirmText: confirmText,
mode: DialogMode.text,
2025-08-19 11:06:16 +08:00
onConfirm: onConfirm,
);
},
);
return result == true;
}
/// 只有“确定”按钮的文字提示弹窗(适合提示信息)
static Future<void> showAlert(
BuildContext context, {
required String title,
String content = '',
String confirmText = '确定',
bool barrierDismissible = true,
2025-08-19 11:06:16 +08:00
final VoidCallback? onConfirm,
}) async {
await showDialog<void>(
context: context,
barrierDismissible: barrierDismissible,
builder: (_) {
return CustomAlertDialog(
title: title,
content: content,
cancelText: '', // 隐藏取消按钮使其成为单按钮
confirmText: confirmText,
mode: DialogMode.text,
2025-08-19 11:06:16 +08:00
onConfirm: onConfirm,
);
},
);
}
/// 输入对话框(带输入框),返回用户输入的字符串;取消或关闭返回 null
static Future<String?> showInput(
BuildContext context, {
required String title,
String hintText = '',
String cancelText = '取消',
String confirmText = '确定',
bool barrierDismissible = true,
}) async {
final result = await showDialog<String?>(
context: context,
barrierDismissible: barrierDismissible,
builder: (_) {
return CustomAlertDialog(
title: title,
hintText: hintText,
cancelText: cancelText,
confirmText: confirmText,
mode: DialogMode.input,
);
},
);
return result;
}
2025-07-28 14:22:07 +08:00
}
class _CustomAlertDialogState extends State<CustomAlertDialog> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
// 输入模式下初始化 TextField 控制器
2025-07-28 14:22:07 +08:00
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
bool get hasCancel => widget.cancelText.trim().isNotEmpty;
2025-07-22 13:34:34 +08:00
2025-07-28 14:22:07 +08:00
@override
Widget build(BuildContext context) {
2025-07-16 08:37:08 +08:00
return Dialog(
backgroundColor: Colors.transparent,
child: Container(
constraints: const BoxConstraints(minWidth: 280),
2025-07-16 08:37:08 +08:00
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
2025-07-16 08:37:08 +08:00
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
2025-07-22 13:34:34 +08:00
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,
),
2025-07-16 08:37:08 +08:00
),
const SizedBox(height: 16),
2025-07-28 14:22:07 +08:00
// ★ 根据 mode 决定展示文字还是输入框 ★
if (widget.mode == DialogMode.text)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
2025-07-28 14:22:07 +08:00
child: Text(
widget.content,
style: const TextStyle(fontSize: 16, color: Colors.black54),
2025-07-28 14:22:07 +08:00
textAlign: TextAlign.center,
),
)
else
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextField(
controller: _controller,
autofocus: true,
2025-07-28 14:22:07 +08:00
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,
),
),
2025-07-22 13:34:34 +08:00
),
2025-07-16 08:37:08 +08:00
),
2025-07-28 14:22:07 +08:00
2025-07-16 08:37:08 +08:00
const SizedBox(height: 20),
const Divider(height: 1),
2025-07-28 14:22:07 +08:00
hasCancel ? _buildDoubleButtons(context) : _buildSingleButton(context),
2025-07-22 13:34:34 +08:00
],
),
),
);
}
Widget _buildDoubleButtons(BuildContext context) {
return Row(
children: [
2025-07-28 14:22:07 +08:00
// 取消
2025-07-22 13:34:34 +08:00
Expanded(
child: InkWell(
onTap: () {
// 根据模式返回不同值:文本模式返回 false输入模式返回 null
final ret = widget.mode == DialogMode.text ? false : null;
// 先触发回调(如果开发者传了),再关闭并把结果返回给调用者
2025-07-28 14:22:07 +08:00
widget.onCancel?.call();
Navigator.of(context).pop(ret);
2025-07-22 13:34:34 +08:00
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
child: Text(
2025-07-28 14:22:07 +08:00
widget.cancelText,
2025-07-22 13:34:34 +08:00
style: const TextStyle(
fontWeight: FontWeight.w500,
color: Colors.black87,
2025-07-22 13:34:34 +08:00
fontSize: 18,
2025-07-16 08:37:08 +08:00
),
2025-07-22 13:34:34 +08:00
),
),
),
),
2025-07-28 14:22:07 +08:00
2025-07-22 13:34:34 +08:00
Container(width: 1, height: 48, color: Colors.grey[300]),
2025-07-28 14:22:07 +08:00
// 确定
2025-07-22 13:34:34 +08:00
Expanded(
child: InkWell(
onTap: () {
2025-07-28 14:22:07 +08:00
if (widget.mode == DialogMode.text) {
widget.onConfirm?.call();
Navigator.of(context).pop(true);
2025-07-28 14:22:07 +08:00
} else {
final value = _controller.text.trim();
widget.onInputConfirm?.call(value);
Navigator.of(context).pop(value);
2025-07-28 14:22:07 +08:00
}
2025-07-22 13:34:34 +08:00
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
child: Text(
2025-07-28 14:22:07 +08:00
widget.confirmText,
2025-07-22 13:34:34 +08:00
style: const TextStyle(
2025-07-28 14:22:07 +08:00
color: Color(0xFF3874F6),
2025-07-22 13:34:34 +08:00
fontWeight: FontWeight.w500,
fontSize: 18,
2025-07-16 08:37:08 +08:00
),
2025-07-22 13:34:34 +08:00
),
2025-07-16 08:37:08 +08:00
),
2025-07-22 13:34:34 +08:00
),
),
],
);
}
Widget _buildSingleButton(BuildContext context) {
return InkWell(
onTap: () {
2025-07-28 14:22:07 +08:00
if (widget.mode == DialogMode.text) {
widget.onConfirm?.call();
Navigator.of(context).pop(true);
2025-07-28 14:22:07 +08:00
} else {
final value = _controller.text.trim();
widget.onInputConfirm?.call(value);
Navigator.of(context).pop(value);
2025-07-28 14:22:07 +08:00
}
2025-07-22 13:34:34 +08:00
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 14),
alignment: Alignment.center,
child: Text(
2025-07-28 14:22:07 +08:00
widget.confirmText,
2025-07-22 13:34:34 +08:00
style: const TextStyle(
2025-07-28 14:22:07 +08:00
color: Color(0xFF3874F6),
2025-07-22 13:34:34 +08:00
fontWeight: FontWeight.w500,
fontSize: 18,
),
2025-07-16 08:37:08 +08:00
),
),
);
}
}