Files
flutterApp/lib/components/input_confirm_dialog.dart

141 lines
5.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
/// 通用带输入框的确认弹窗组件
/// [title]:弹窗标题
/// [hintText]:输入框提示文字
/// [labelText]:输入框标签文字
/// [confirmText]:确认按钮文字(默认"保存")
/// [cancelText]:取消按钮文字(默认"取消")
/// [onConfirm]:确认按钮回调,参数为输入的文本,返回bool表示是否关闭弹窗
/// [inputValidator]:输入内容校验器,返回null表示校验通过,返回字符串表示错误提示
/// [maxWidth]:弹窗内容宽约束;横屏页面传一个较小的值,避免 AlertDialog 被输入框撑满屏幕
Future<void> showInputConfirmDialog({
required BuildContext context,
required String title,
required String hintText,
String labelText = '',
String confirmText = '保存',
String cancelText = '取消',
required Function(String inputText) onConfirm,
String? Function(String inputText)? inputValidator,
double? maxWidth,
String? initialText,
}) async {
// 输入框控制器(initialText 非空时预填,用于改名等回显场景)
final TextEditingController controller = TextEditingController(
text: initialText ?? '',
);
await showDialog(
context: context,
builder: (BuildContext context) {
// 横屏页面(如区域编辑器)键盘常占半屏:AlertDialog 的 title/actions 不随
// scrollable 滚动,短屏下会被撑出 RenderFlex 溢出、输入框/按钮被裁而“不好改”。
// 改为自定义 Dialog:键盘弹出时贴顶对齐 + 整体限高 + 字段区可滚,按钮永远可见;
// 没键盘时(如竖屏)仍居中,不改变原有观感。
final MediaQueryData mq = MediaQuery.of(context);
final double keyboardInset = mq.viewInsets.bottom;
final bool lifted = keyboardInset > 0;
// 弹窗可用最大高度:屏高 - 键盘 - 顶部安全区,再夹到一个合理区间
final double maxDialogHeight = (mq.size.height - keyboardInset - 24).clamp(
140.0,
520.0,
);
final double contentWidth = (maxWidth ?? 280) + 40; // 加左右 20 padding
return Dialog(
alignment: lifted ? Alignment.topCenter : Alignment.center,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
insetPadding: EdgeInsets.fromLTRB(
24,
mq.padding.top + (lifted ? 12 : 24),
24,
keyboardInset + 12,
),
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxDialogHeight,
maxWidth: contentWidth,
),
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
title,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 12),
// 字段区单独可滚:极矮屏下也能滚到输入框,不会把标题/按钮顶出去
Flexible(
child: SingleChildScrollView(
child: TextField(
controller: controller,
decoration: InputDecoration(
hintText: hintText,
border: const OutlineInputBorder(),
labelText: labelText,
),
autofocus: true, // 自动聚焦输入框
),
),
),
const SizedBox(height: 6),
// 按钮行固定在底部,始终可见可点
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// 取消按钮
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(cancelText),
),
// 确认按钮
TextButton(
onPressed: () {
// 获取输入文本并去空格
final String inputText = controller.text.trim();
// 1. 执行自定义校验
if (inputValidator != null) {
final String? errorMsg = inputValidator(inputText);
if (errorMsg != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorMsg),
backgroundColor: Colors.red,
),
);
return;
}
}
// 2. 执行确认回调
onConfirm(inputText);
// 3. 关闭弹窗
Navigator.of(context).pop();
},
child: Text(confirmText),
),
],
),
],
),
),
),
);
},
);
}