flutter_integrated_whb/lib/customWidget/custom_alert_dialog.dart

312 lines
9.6 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.

import 'package:flutter/material.dart';
/// 对话框模式
enum DialogMode { text, input }
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; // 对话框模式
/// 如果 force 为 true弹窗为强制模式不可点背景关闭不响应返回键并只显示确定按钮
final bool force;
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,
}) : super(key: key);
@override
_CustomAlertDialogState createState() => _CustomAlertDialogState();
// ------------------ 快捷静态方法 ------------------
/// 带“取消/确定”的确认弹窗
/// 返回 true = 确定false = 取消或关闭
static Future<bool> showConfirm(
BuildContext context, {
required String title,
String content = '',
String cancelText = '取消',
String confirmText = '确定',
bool barrierDismissible = true,
final VoidCallback? onConfirm,
bool force = false, // 新参数:强制模式
}) async {
final result = await showDialog<bool>(
context: context,
// force 优先控制是否可通过点击蒙层关闭
barrierDismissible: force ? false : barrierDismissible,
builder: (_) {
return CustomAlertDialog(
title: title,
content: content,
cancelText: cancelText,
confirmText: confirmText,
mode: DialogMode.text,
onConfirm: onConfirm,
force: force,
);
},
);
return result == true;
}
/// 只有“确定”按钮的文字提示弹窗(适合提示信息)
static Future<void> showAlert(
BuildContext context, {
required String title,
String content = '',
String confirmText = '确定',
bool barrierDismissible = true,
final VoidCallback? onConfirm,
bool force = false, // 新参数:强制模式
}) async {
await showDialog<void>(
context: context,
barrierDismissible: force ? false : barrierDismissible,
builder: (_) {
return CustomAlertDialog(
title: title,
content: content,
cancelText: '', // 隐藏取消按钮使其成为单按钮
confirmText: confirmText,
mode: DialogMode.text,
onConfirm: onConfirm,
force: force,
);
},
);
}
/// 输入对话框(带输入框),返回用户输入的字符串;取消或关闭返回 null
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: (_) {
return CustomAlertDialog(
title: title,
hintText: hintText,
cancelText: cancelText,
confirmText: confirmText,
mode: DialogMode.input,
force: force,
);
},
);
return result;
}
}
class _CustomAlertDialogState extends State<CustomAlertDialog> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
// 输入模式下初始化 TextField 控制器
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
/// 当 force 为 true 时,始终隐藏取消按钮
bool get hasCancel => !widget.force && widget.cancelText.trim().isNotEmpty;
/// 统一关闭 dialog 的方法(带安全 mount 检查)
void _closeDialog([dynamic result]) {
if (!mounted) return;
Navigator.of(context).pop(result);
}
@override
Widget build(BuildContext context) {
// 使用 WillPopScope 禁止返回键关闭(当 force 为 true
return WillPopScope(
onWillPop: () async {
// 如果是强制模式,禁止返回(返回 false否则允许true
return !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),
// ★ 根据 mode 决定展示文字还是输入框 ★
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
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,
),
),
),
),
const SizedBox(height: 20),
const Divider(height: 1),
hasCancel ? _buildDoubleButtons(context) : _buildSingleButton(context),
],
),
),
),
);
}
Widget _buildDoubleButtons(BuildContext context) {
return Row(
children: [
// 取消
Expanded(
child: InkWell(
onTap: () {
// 根据模式返回不同值:文本模式返回 false输入模式返回 null
final ret = widget.mode == DialogMode.text ? false : null;
// 先触发回调(如果开发者传了),再关闭并把结果返回给调用者
widget.onCancel?.call();
_closeDialog(ret);
},
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(0xFF3874F6),
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(0xFF3874F6),
fontWeight: FontWeight.w500,
fontSize: 18,
),
),
),
);
}
}