You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

224 lines
7.1 KiB

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../../core/theme/app_palette.dart';
import '../../../data/services/id_verify_service.dart';
/// 实名认证输入弹窗:姓名 + 身份证号。
///
/// 已实名时进来就是「修改认证」——姓名预填上次的值,身份证号必须重新输入
/// (服务端只存掩码,拿不回原文,也不该拿回)。
///
/// 返回 true 表示这次认证通过。
Future<bool> showIdVerifyDialog({String? reasonText}) async {
final ok = await Get.dialog<bool>(
IdVerifyDialog(reasonText: reasonText),
barrierDismissible: false,
);
return ok == true;
}
/// ⚠️ 必须是 StatefulWidget:TextEditingController 要由 State 自己持有、
/// 在 State.dispose() 里释放。
///
/// 早先的写法是在外面 `await Get.dialog(...)` 之后立刻 dispose——那是错的:
/// Get.dialog 在路由 pop 的瞬间就返回,而弹窗此时还在播退场动画、里面的
/// TextField 仍挂在树上,controller 被提前释放会破坏元素树,
/// 表现为红屏 `'_dependents.isEmpty': is not true`。
class IdVerifyDialog extends StatefulWidget {
const IdVerifyDialog({Key? key, this.reasonText}) : super(key: key);
final String? reasonText;
@override
State<IdVerifyDialog> createState() => _IdVerifyDialogState();
}
class _IdVerifyDialogState extends State<IdVerifyDialog> {
late final bool _isModify;
late final TextEditingController _nameCtrl;
late final TextEditingController _idCtrl;
String _error = '';
bool _busy = false;
@override
void initState() {
super.initState();
final svc = IdVerifyService.to;
_isModify = svc.verified.value;
_nameCtrl = TextEditingController(text: _isModify ? svc.realname.value : '');
_idCtrl = TextEditingController();
}
@override
void dispose() {
_nameCtrl.dispose();
_idCtrl.dispose();
super.dispose();
}
Future<void> _submit() async {
if (_busy) return;
setState(() {
_busy = true;
_error = '';
});
final msg = await IdVerifyService.to.submit(_nameCtrl.text, _idCtrl.text);
// 请求回来时弹窗可能已经被关掉了,不能再 setState。
if (!mounted) return;
if (msg == null) {
Navigator.of(context).pop(true);
return;
}
setState(() {
_busy = false;
_error = msg;
});
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return AlertDialog(
backgroundColor: AppPalette.surface(isDark),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.r)),
title: Text(
_isModify ? 'idVerifyModifyTitle'.tr : 'idVerifyTitle'.tr,
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: AppPalette.textPrimary(isDark),
),
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.reasonText ?? 'idVerifyDesc'.tr,
style: TextStyle(
fontSize: 13.sp,
height: 1.6,
color: AppPalette.textSecondary(isDark),
),
),
SizedBox(height: 16.h),
_field(
isDark: isDark,
controller: _nameCtrl,
label: 'idVerifyNameLabel'.tr,
hint: 'idVerifyNameHint'.tr,
maxLength: 32,
),
SizedBox(height: 12.h),
_field(
isDark: isDark,
controller: _idCtrl,
label: 'idVerifyIdLabel'.tr,
hint: _isModify ? 'idVerifyIdHintModify'.tr : 'idVerifyIdHint'.tr,
maxLength: 18,
// 身份证号只有数字和末位 X
formatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9xX]')),
_UpperCaseFormatter(),
],
),
if (_error.isNotEmpty)
Padding(
padding: EdgeInsets.only(top: 10.h),
child: Text(
_error,
style: TextStyle(fontSize: 12.sp, color: Colors.redAccent),
),
),
SizedBox(height: 10.h),
Text(
'idVerifyPrivacyTip'.tr,
style: TextStyle(
fontSize: 11.sp,
height: 1.5,
color: AppPalette.textHint(isDark),
),
),
],
),
),
actions: [
TextButton(
onPressed: _busy ? null : () => Navigator.of(context).pop(false),
child: Text('cancel'.tr,
style: TextStyle(
fontSize: 14.sp, color: AppPalette.textHint(isDark))),
),
TextButton(
onPressed: _busy ? null : _submit,
child: _busy
? SizedBox(
width: 16.w,
height: 16.w,
child: const CircularProgressIndicator(strokeWidth: 2),
)
: Text('idVerifySubmit'.tr,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: AppPalette.accent)),
),
],
);
}
Widget _field({
required bool isDark,
required TextEditingController controller,
required String label,
required String hint,
required int maxLength,
List<TextInputFormatter>? formatters,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: TextStyle(
fontSize: 12.sp, color: AppPalette.textSecondary(isDark))),
SizedBox(height: 6.h),
TextField(
controller: controller,
maxLength: maxLength,
inputFormatters: formatters,
style:
TextStyle(fontSize: 14.sp, color: AppPalette.textPrimary(isDark)),
decoration: InputDecoration(
counterText: '',
hintText: hint,
hintStyle:
TextStyle(fontSize: 13.sp, color: AppPalette.textHint(isDark)),
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(10.r)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.r),
borderSide: BorderSide(color: AppPalette.textHint(isDark)),
),
),
),
],
);
}
}
/// 身份证末位的 x 统一成大写 X,省得因为大小写把合法号码判成非法。
class _UpperCaseFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
return newValue.copyWith(text: newValue.text.toUpperCase());
}
}