import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../../core/i18n/app_localizations.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors_extension.dart'; import '../../../core/data/mock_api_data.dart'; import '../expense_api.dart'; import '../../../shared/widgets/attachment_picker.dart'; /// 报销明细输入数据。 class ExpenseDetailInputData { final String category; final String categoryName; final String acctSubjectId; final String acctSubjectName; final String purpose; final double amount; // 含税金额 final double taxRate; final String projectId; final String projectName; final String costDeptId; final String costDeptName; final String customerVendorId; final String customerVendorName; final double offsetAmount; final String bankName; final String bankAccountName; final String bankAccount; final String remark; final List attachmentPaths; final String sqMan; final String sqManName; final String aeNo; final String aeDd; const ExpenseDetailInputData({ required this.category, required this.categoryName, required this.acctSubjectId, required this.acctSubjectName, required this.purpose, required this.amount, required this.taxRate, this.projectId = '', this.projectName = '', this.costDeptId = '', this.costDeptName = '', this.customerVendorId = '', this.customerVendorName = '', this.offsetAmount = 0.0, this.bankName = '', this.bankAccountName = '', this.bankAccount = '', this.remark = '', this.attachmentPaths = const [], this.sqMan = '', this.sqManName = '', this.aeNo = '', this.aeDd = '', }); } /// 报销明细编辑弹窗。 /// /// 使用 [TDSlidePopupRoute] 从底部滑出,卡片化展示表单字段。 /// 参照 ExpenseApplyCreatePage 的 ExpenseDetailDialog 样式。 class ExpenseDetailDialog extends StatefulWidget { final List categories; final List projects; final List costDepts; final List customers; final List employees; final AppLocalizations l10n; final ExpenseDetailInputData? initialData; const ExpenseDetailDialog({ super.key, required this.categories, required this.projects, required this.costDepts, required this.customers, required this.employees, required this.l10n, this.initialData, }); /// 显示弹窗,返回 [ExpenseDetailInputData] 或 `null`(取消时)。 static Future show( BuildContext context, { required List categories, required List projects, required List costDepts, required List customers, required List employees, required AppLocalizations l10n, ExpenseDetailInputData? initialData, }) { FocusScope.of(context).unfocus(); return Navigator.push( context, TDSlidePopupRoute( slideTransitionFrom: SlideTransitionFrom.bottom, isDismissible: false, builder: (_) => ExpenseDetailDialog( categories: categories, projects: projects, costDepts: costDepts, customers: customers, employees: employees, l10n: l10n, initialData: initialData, ), ), ); } @override State createState() => _ExpenseDetailDialogState(); } class _ExpenseDetailDialogState extends State { late String _cat; late String _catLabel; late TextEditingController _descCtrl; late TextEditingController _amountCtrl; CustomerVendor? _selCustomer; late TextEditingController _offsetCtrl; late TextEditingController _remarkCtrl; late TextEditingController _bankNameCtrl; late TextEditingController _bankAccountNameCtrl; late TextEditingController _bankAccountCtrl; double _taxRate = 0.06; Project? _selProject; CostDept? _selDept; EmployeeItem? _selEmployee; late final AttachmentPickerController _attachmentCtrl; final ScrollController _scrollCtrl = ScrollController(); static const _taxOptions = [0.06, 0.09, 0.13]; static const _taxLabels = ['6%', '9%', '13%']; List get _cats => widget.categories; AppLocalizations get _l10n => widget.l10n; CostCategory get _selCat => _cats.firstWhere((c) => c.code == _cat); bool get _isEdit => widget.initialData != null; @override void initState() { super.initState(); final d = widget.initialData; _cat = d != null ? (_cats.any((c) => c.code == d.category) ? d.category : _cats.first.code) : _cats.isNotEmpty ? _cats.first.code : 'other'; _catLabel = _l10n.get(_cats.firstWhere((c) => c.code == _cat).nameKey); _descCtrl = TextEditingController(text: d?.purpose ?? ''); _amountCtrl = TextEditingController(text: d != null && d.amount > 0 ? d.amount.toStringAsFixed(2) : ''); if (d != null && d.customerVendorName.isNotEmpty) { _selCustomer = CustomerVendor(id: '', name: d.customerVendorName); } _offsetCtrl = TextEditingController(text: d != null && d.offsetAmount > 0 ? d.offsetAmount.toStringAsFixed(2) : ''); _remarkCtrl = TextEditingController(text: d?.remark ?? ''); _bankNameCtrl = TextEditingController(text: d?.bankName ?? ''); _bankAccountNameCtrl = TextEditingController(text: d?.bankAccountName ?? ''); _bankAccountCtrl = TextEditingController(text: d?.bankAccount ?? ''); _taxRate = d?.taxRate ?? 0.13; if (d != null && d.sqMan.isNotEmpty && widget.employees.isNotEmpty) { final idx = widget.employees.indexWhere((e) => e.salNo == d.sqMan); if (idx >= 0) _selEmployee = widget.employees[idx]; } if (d != null) { if (d.projectId.isNotEmpty && widget.projects.isNotEmpty) { _selProject = widget.projects.firstWhere((p) => p.id.toString() == d.projectId, orElse: () => widget.projects.first); } if (d.costDeptId.isNotEmpty && widget.costDepts.isNotEmpty) { _selDept = widget.costDepts.firstWhere((dept) => dept.id == d.costDeptId, orElse: () => widget.costDepts.first); } if (d.attachmentPaths.isNotEmpty) { // Restore attachments will be handled after build WidgetsBinding.instance.addPostFrameCallback((_) { _attachmentCtrl.restoreFromPaths(d.attachmentPaths); }); } } _attachmentCtrl = AttachmentPickerController(maxCount: 9) ..addListener(() => setState(() {})); } @override void dispose() { _descCtrl.dispose(); _amountCtrl.dispose(); _offsetCtrl.dispose(); _remarkCtrl.dispose(); _bankNameCtrl.dispose(); _bankAccountNameCtrl.dispose(); _bankAccountCtrl.dispose(); _attachmentCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } void _confirm() { final amount = double.tryParse(_amountCtrl.text) ?? 0; final desc = _descCtrl.text.trim(); if (desc.isEmpty) { TDToast.showText(_l10n.get('enterExpenseName'), context: context); return; } if (amount <= 0) { TDToast.showText(_l10n.get('amountPositive'), context: context); return; } Navigator.pop( context, ExpenseDetailInputData( category: _cat, categoryName: _l10n.get(_selCat.nameKey), acctSubjectId: _selCat.acctSubjectId, acctSubjectName: _selCat.acctSubjectName, purpose: desc, amount: amount, taxRate: _taxRate, projectId: _selProject?.id.toString() ?? '', projectName: _selProject?.name ?? '', costDeptId: _selDept?.id ?? '', costDeptName: _selDept?.name ?? '', customerVendorId: _selCustomer?.id ?? '', customerVendorName: _selCustomer?.name ?? '', offsetAmount: double.tryParse(_offsetCtrl.text) ?? 0, bankName: _bankNameCtrl.text.trim(), bankAccountName: _bankAccountNameCtrl.text.trim(), bankAccount: _bankAccountCtrl.text.trim(), remark: _remarkCtrl.text.trim(), attachmentPaths: _attachmentCtrl.toPathList(), sqMan: _selEmployee?.salNo ?? '', sqManName: _selEmployee?.name ?? '', aeNo: widget.initialData?.aeNo ?? '', aeDd: widget.initialData?.aeDd ?? '', ), ); } double get _amountExclTax => _taxRate > 0 ? (double.tryParse(_amountCtrl.text) ?? 0) / (1 + _taxRate) : (double.tryParse(_amountCtrl.text) ?? 0); double get _taxAmount => (double.tryParse(_amountCtrl.text) ?? 0) - _amountExclTax; @override Widget build(BuildContext context) { final colors = Theme.of(context).extension()!; final bottomInset = MediaQuery.of(context).viewInsets.bottom; return SafeArea( child: ConstrainedBox( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, ), child: Padding( padding: EdgeInsets.only(bottom: bottomInset), child: Container( decoration: BoxDecoration( color: colors.bgPage, borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildHeader(colors), Flexible( child: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), behavior: HitTestBehavior.translucent, child: SingleChildScrollView( controller: _scrollCtrl, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_isEdit && widget.initialData!.aeNo.isNotEmpty) ...[ _buildAeInfoCard(colors), const SizedBox(height: 12), ], _buildCategoryCard(colors), const SizedBox(height: 12), _buildAcctSubjectCard(colors), const SizedBox(height: 12), _buildPurposeInput(colors), const SizedBox(height: 12), _buildAmountCard(), const SizedBox(height: 12), _buildTaxRateCard(colors), if ((double.tryParse(_amountCtrl.text) ?? 0) > 0) ...[ const SizedBox(height: 12), _buildCalcInfo(colors), ], const SizedBox(height: 12), _buildProjectCard(colors), const SizedBox(height: 12), _buildCostDeptCard(colors), const SizedBox(height: 12), _buildEmployeeCard(colors), const SizedBox(height: 12), _buildBankInfoCard(colors), const SizedBox(height: 12), _buildCustomerCard(colors), const SizedBox(height: 12), _buildOffsetCard(), const SizedBox(height: 12), _buildRemarkInput(colors), const SizedBox(height: 12), _buildAttachmentCard(colors), ], ), ), ), ), Container( padding: const EdgeInsets.fromLTRB(16, 12, 16, 16), decoration: BoxDecoration( color: colors.bgCard, border: Border( top: BorderSide(color: colors.border, width: 0.5), ), ), child: _buildActions(), ), ], ), ), ), ), ); } // ── 标题栏 ── Widget _buildHeader(AppColorsExtension colors) { return Column( mainAxisSize: MainAxisSize.min, children: [ Center( child: Container( margin: const EdgeInsets.only(top: 8, bottom: 4), width: 36, height: 4, decoration: BoxDecoration( color: colors.border, borderRadius: BorderRadius.circular(2), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(20, 8, 12, 16), child: Row( children: [ const SizedBox(width: 28), Expanded( child: Center( child: Text( _l10n.get('addExpenseDetail'), style: TextStyle( fontSize: AppFontSizes.title, fontWeight: FontWeight.w600, color: colors.textPrimary, ), ), ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Padding( padding: const EdgeInsets.all(4), child: Icon( Icons.close, size: 20, color: colors.textSecondary, ), ), ), ], ), ), ], ); } // ── picker 卡片 ── Widget _pickerCard({ required String label, required bool required, required String currentLabel, required List labels, required ValueChanged onSelected, required AppColorsExtension colors, VoidCallback? onClear, }) { final tdTheme = TDTheme.of(context); final hasValue = onClear != null; return GestureDetector( onTap: () { TDPicker.showMultiPicker( context, title: label, backgroundColor: colors.bgCard, data: [labels], onConfirm: (selected) { if (selected.isNotEmpty && selected[0] is int) { final idx = selected[0] as int; if (idx >= 0 && idx < labels.length) { Navigator.of(context).pop(); onSelected(idx); } } }, ); }, child: Container( padding: const EdgeInsets.only(left: 16, right: 10, top: 12, bottom: 12), decoration: BoxDecoration( color: tdTheme.bgColorContainer, borderRadius: BorderRadius.circular(tdTheme.radiusDefault), border: Border.all(color: tdTheme.componentStrokeColor), ), child: Row( children: [ TDText(label, maxLines: 1, overflow: TextOverflow.visible, font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, style: const TextStyle(letterSpacing: 0)), if (required) Padding(padding: const EdgeInsets.only(left: 4), child: TDText('*', font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, style: TextStyle(color: tdTheme.errorColor6))), const SizedBox(width: 12), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.max, children: [ Flexible(child: TDText(currentLabel, maxLines: 1, overflow: TextOverflow.ellipsis, font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, textColor: tdTheme.textColorPrimary, textAlign: TextAlign.end)), const SizedBox(width: 4), SizedBox( width: 18, height: 18, child: hasValue ? GestureDetector(onTap: onClear, child: Icon(Icons.close, size: 18, color: tdTheme.textColorPlaceholder)) : Icon(Icons.chevron_right, size: 18, color: tdTheme.textColorPlaceholder), ), ]), ), ], ), ), ); } // ── 费用类别 ── Widget _buildCategoryCard(AppColorsExtension colors) { return _pickerCard( label: _l10n.get('expenseCategory'), required: true, currentLabel: _catLabel, labels: _cats.map((c) => _l10n.get(c.nameKey)).toList(), colors: colors, onSelected: (idx) => setState(() { _cat = _cats[idx].code; _catLabel = _l10n.get(_cats[idx].nameKey); }), ); } // ── 输入卡片(对齐 pickerCard 样式) ── Widget _inputCard({ required String label, required bool required, required TextEditingController controller, required String hintText, required AppColorsExtension colors, TextInputType? keyboardType, List? inputFormatters, }) { final tdTheme = TDTheme.of(context); final hasValue = controller.text.isNotEmpty; return Container( padding: const EdgeInsets.only(left: 16, right: 10, top: 12, bottom: 12), decoration: BoxDecoration( color: tdTheme.bgColorContainer, borderRadius: BorderRadius.circular(tdTheme.radiusDefault), border: Border.all(color: tdTheme.componentStrokeColor), ), child: Row( children: [ TDText(label, maxLines: 1, overflow: TextOverflow.visible, font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, style: const TextStyle(letterSpacing: 0)), if (required) Padding(padding: const EdgeInsets.only(left: 4), child: TDText('*', font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, style: TextStyle(color: tdTheme.errorColor6))), const SizedBox(width: 12), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.max, children: [ Flexible( child: TextField( controller: controller, textAlign: TextAlign.end, keyboardType: keyboardType, inputFormatters: inputFormatters, style: TextStyle(fontSize: 16, color: colors.textPrimary), decoration: InputDecoration( hintText: hintText, hintStyle: TextStyle(fontSize: 16, color: colors.textPlaceholder), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, ), onChanged: (_) => setState(() {}), ), ), const SizedBox(width: 4), SizedBox( width: 18, height: 18, child: hasValue ? GestureDetector( onTap: () { controller.clear(); setState(() {}); }, child: Icon(Icons.close, size: 18, color: tdTheme.textColorPlaceholder), ) : null, ), ]), ), ], ), ); } // ── 费用事由 ── Widget _buildPurposeInput(AppColorsExtension colors) { final tdTheme = TDTheme.of(context); return TDTextarea( controller: _descCtrl, label: _l10n.get('feeReason'), required: true, hintText: _l10n.get('enterFeeReason'), maxLines: 3, minLines: 1, maxLength: 500, indicator: true, decoration: BoxDecoration( color: tdTheme.bgColorContainer, borderRadius: BorderRadius.circular(tdTheme.radiusDefault), border: Border.all(color: tdTheme.componentStrokeColor), ), onChanged: (_) => setState(() {}), ); } // ── 含税金额 ── Widget _buildAmountCard() { return _inputCard( label: _l10n.get('amountInclTax'), required: true, controller: _amountCtrl, hintText: '>0', colors: Theme.of(context).extension()!, keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$'))], ); } // ── 税率 ── Widget _buildTaxRateCard(AppColorsExtension colors) { final currentLabel = '${(_taxRate * 100).toStringAsFixed(0)}%'; return _pickerCard( label: _l10n.get('taxRate'), required: true, currentLabel: currentLabel, labels: _taxLabels.toList(), colors: colors, onSelected: (idx) => setState(() { _taxRate = _taxOptions[idx]; }), ); } // ── 计算信息 ── Widget _buildCalcInfo(AppColorsExtension colors) { final amount = double.tryParse(_amountCtrl.text) ?? 0; if (amount <= 0) return const SizedBox.shrink(); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: colors.primaryLight, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded( child: Text( '${_l10n.get('amountExcludingTax')}: ¥${_amountExclTax.toStringAsFixed(2)}', style: TextStyle( fontSize: AppFontSizes.body, color: colors.textSecondary, ), ), ), Text( '${_l10n.get('taxAmount')}: ¥${_taxAmount.toStringAsFixed(2)}', style: TextStyle( fontSize: AppFontSizes.body, color: colors.textSecondary, ), ), ], ), ); } // ── 导入单据信息 ── Widget _buildAeInfoCard(AppColorsExtension colors) { final d = widget.initialData!; final tdTheme = TDTheme.of(context); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: _cardDecoration(tdTheme), child: Column( children: [ _readOnlyRow(tdTheme, _l10n.get('expenseApplyNo'), d.aeNo), const SizedBox(height: 8), _readOnlyRow(tdTheme, _l10n.get('applyDate'), d.aeDd), ], ), ); } Widget _readOnlyRow(TDThemeData tdTheme, String label, String value) { return Row(children: [ TDText(label, font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, style: const TextStyle(letterSpacing: 0)), const SizedBox(width: 12), Expanded(child: TDText(value, maxLines: 1, overflow: TextOverflow.ellipsis, font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, textColor: tdTheme.textColorPrimary, textAlign: TextAlign.end)), ]); } BoxDecoration _cardDecoration(TDThemeData tdTheme) => BoxDecoration( color: tdTheme.bgColorContainer, borderRadius: BorderRadius.circular(tdTheme.radiusDefault), border: Border.all(color: tdTheme.componentStrokeColor), ); // ── 申请人 ── Widget _buildEmployeeCard(AppColorsExtension colors) { final employees = widget.employees; return _pickerCard( label: _l10n.get('applicant'), required: false, currentLabel: _selEmployee != null ? '${_selEmployee!.salNo}/${_selEmployee!.name}' : _l10n.get('pleaseSelect'), labels: employees.map((e) => '${e.salNo}/${e.name}').toList(), colors: colors, onSelected: (idx) => setState(() { _selEmployee = employees[idx]; _bankNameCtrl.text = _selEmployee!.bnkNo; _bankAccountNameCtrl.text = _selEmployee!.accName; _bankAccountCtrl.text = _selEmployee!.bnkId; }), onClear: _selEmployee != null ? () => setState(() { _selEmployee = null; _bankNameCtrl.clear(); _bankAccountNameCtrl.clear(); _bankAccountCtrl.clear(); }) : null, ); } // ── 客户/厂商 ── Widget _buildCustomerCard(AppColorsExtension colors) { final vendors = widget.customers; return _pickerCard( label: _l10n.get('customerVendor'), required: false, currentLabel: _selCustomer?.name ?? _l10n.get('pleaseSelect'), labels: vendors.map((v) => v.name).toList(), colors: colors, onSelected: (idx) => setState(() => _selCustomer = vendors[idx]), onClear: _selCustomer != null ? () => setState(() => _selCustomer = null) : null, ); } // ── 已充金额 ── Widget _buildOffsetCard() { return _inputCard( label: _l10n.get('offsetAmount'), required: false, controller: _offsetCtrl, hintText: '0', colors: Theme.of(context).extension()!, keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$'))], ); } // ── 备注 ── Widget _buildRemarkInput(AppColorsExtension colors) { final tdTheme = TDTheme.of(context); return TDTextarea( controller: _remarkCtrl, label: _l10n.get('remark'), hintText: _l10n.get('enterRemark'), maxLines: 3, minLines: 1, maxLength: 500, indicator: true, decoration: BoxDecoration( color: tdTheme.bgColorContainer, borderRadius: BorderRadius.circular(tdTheme.radiusDefault), border: Border.all(color: tdTheme.componentStrokeColor), ), onChanged: (_) => setState(() {}), ); } // ── 操作按钮 ── Widget _buildAttachmentCard(AppColorsExtension colors) { final tdTheme = TDTheme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 4), child: TDText( _l10n.get('attachmentUpload'), font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, style: const TextStyle(letterSpacing: 0), ), ), const SizedBox(height: 4), AttachmentPicker( controller: _attachmentCtrl, maxImageSizeMB: 10, maxFileSizeMB: 20, allowedExtensions: const ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'], onFileRejected: (file, reason) { if (context.mounted) TDToast.showText(reason, context: context); }, ), ], ); } // ── 会计科目(只读,选择类别后自动带出) ── Widget _buildAcctSubjectCard(AppColorsExtension colors) { return _readOnlyCard( label: _l10n.get('acctSubject'), value: '${_selCat.acctSubjectId} ${_selCat.acctSubjectName}', colors: colors, ); } Widget _readOnlyCard({ required String label, required String value, required AppColorsExtension colors, }) { final tdTheme = TDTheme.of(context); return Container( padding: const EdgeInsets.only(left: 16, right: 16, top: 12, bottom: 12), decoration: BoxDecoration( color: tdTheme.bgColorContainer, borderRadius: BorderRadius.circular(tdTheme.radiusDefault), border: Border.all(color: tdTheme.componentStrokeColor), ), child: Row( children: [ TDText(label, maxLines: 1, overflow: TextOverflow.visible, font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, style: const TextStyle(letterSpacing: 0)), const SizedBox(width: 12), Expanded( child: TDText(value, maxLines: 1, overflow: TextOverflow.ellipsis, font: tdTheme.fontBodyLarge, fontWeight: FontWeight.w400, textColor: tdTheme.textColorPrimary, textAlign: TextAlign.end), ), ], ), ); } // ── 关联项目 ── Widget _buildProjectCard(AppColorsExtension colors) { final projects = widget.projects; return _pickerCard( label: _l10n.get('relatedProject'), required: false, currentLabel: _selProject?.name ?? _l10n.get('pleaseSelect'), labels: projects.map((p) => p.name).toList(), colors: colors, onSelected: (idx) => setState(() => _selProject = projects[idx]), onClear: _selProject != null ? () => setState(() => _selProject = null) : null, ); } // ── 费用承担部门 ── Widget _buildCostDeptCard(AppColorsExtension colors) { final depts = widget.costDepts; return _pickerCard( label: _l10n.get('costDept'), required: false, currentLabel: _selDept?.name ?? _l10n.get('pleaseSelect'), labels: depts.map((d) => d.name).toList(), colors: colors, onSelected: (idx) => setState(() => _selDept = depts[idx]), onClear: _selDept != null ? () => setState(() => _selDept = null) : null, ); } // ── 收款银行信息 ── Widget _buildBankInfoCard(AppColorsExtension colors) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _inputCard(label: _l10n.get('bankName'), required: false, controller: _bankNameCtrl, hintText: _l10n.get('pleaseEnter'), colors: colors), const SizedBox(height: 12), _inputCard(label: _l10n.get('bankAccountName'), required: false, controller: _bankAccountNameCtrl, hintText: _l10n.get('pleaseEnter'), colors: colors), const SizedBox(height: 12), _inputCard(label: _l10n.get('bankAccount'), required: false, controller: _bankAccountCtrl, hintText: _l10n.get('pleaseEnter'), colors: colors), ], ); } Widget _buildActions() { return Row( children: [ Expanded( child: TDButton( text: _l10n.get('cancel'), size: TDButtonSize.large, type: TDButtonType.outline, shape: TDButtonShape.rectangle, theme: TDButtonTheme.defaultTheme, onTap: () => Navigator.pop(context), ), ), const SizedBox(width: 12), Expanded( child: TDButton( text: _isEdit ? _l10n.get('confirmEdit') : _l10n.get('add'), size: TDButtonSize.large, type: TDButtonType.fill, shape: TDButtonShape.rectangle, theme: TDButtonTheme.primary, onTap: _confirm, ), ), ], ); } }