|
|
@@ -0,0 +1,560 @@
|
|
|
+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';
|
|
|
+
|
|
|
+/// 报销明细输入数据。
|
|
|
+class ExpenseDetailInputData {
|
|
|
+ final String category;
|
|
|
+ final String categoryName;
|
|
|
+ final String expenseDesc;
|
|
|
+ final double amount; // 含税金额
|
|
|
+ final double taxRate;
|
|
|
+ final String customerVendorName;
|
|
|
+ final double offsetAmount;
|
|
|
+ final String remark;
|
|
|
+
|
|
|
+ const ExpenseDetailInputData({
|
|
|
+ required this.category,
|
|
|
+ required this.categoryName,
|
|
|
+ required this.expenseDesc,
|
|
|
+ required this.amount,
|
|
|
+ required this.taxRate,
|
|
|
+ this.customerVendorName = '',
|
|
|
+ this.offsetAmount = 0.0,
|
|
|
+ this.remark = '',
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/// 报销明细编辑弹窗。
|
|
|
+///
|
|
|
+/// 使用 [TDSlidePopupRoute] 从底部滑出,卡片化展示表单字段。
|
|
|
+/// 参照 ExpenseApplicationApplyPage 的 ExpenseDetailDialog 样式。
|
|
|
+class ExpenseDetailEditDialog extends StatefulWidget {
|
|
|
+ final List<CostCategory> categories;
|
|
|
+ final AppLocalizations l10n;
|
|
|
+
|
|
|
+ const ExpenseDetailEditDialog({
|
|
|
+ super.key,
|
|
|
+ required this.categories,
|
|
|
+ required this.l10n,
|
|
|
+ });
|
|
|
+
|
|
|
+ /// 显示弹窗,返回 [ExpenseDetailInputData] 或 `null`(取消时)。
|
|
|
+ static Future<ExpenseDetailInputData?> show(
|
|
|
+ BuildContext context, {
|
|
|
+ required List<CostCategory> categories,
|
|
|
+ required AppLocalizations l10n,
|
|
|
+ }) {
|
|
|
+ FocusScope.of(context).unfocus();
|
|
|
+ return Navigator.push<ExpenseDetailInputData>(
|
|
|
+ context,
|
|
|
+ TDSlidePopupRoute<ExpenseDetailInputData>(
|
|
|
+ slideTransitionFrom: SlideTransitionFrom.bottom,
|
|
|
+ isDismissible: false,
|
|
|
+ builder: (_) => ExpenseDetailEditDialog(
|
|
|
+ categories: categories,
|
|
|
+ l10n: l10n,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<ExpenseDetailEditDialog> createState() =>
|
|
|
+ _ExpenseDetailEditDialogState();
|
|
|
+}
|
|
|
+
|
|
|
+class _ExpenseDetailEditDialogState extends State<ExpenseDetailEditDialog> {
|
|
|
+ late String _cat;
|
|
|
+ late String _catLabel;
|
|
|
+ late TextEditingController _descCtrl;
|
|
|
+ late TextEditingController _amountCtrl;
|
|
|
+ late TextEditingController _customerCtrl;
|
|
|
+ late TextEditingController _offsetCtrl;
|
|
|
+ late TextEditingController _remarkCtrl;
|
|
|
+ double _taxRate = 0.06;
|
|
|
+
|
|
|
+ static const _taxOptions = [0.06, 0.09, 0.13];
|
|
|
+ static const _taxLabels = ['6%', '9%', '13%'];
|
|
|
+
|
|
|
+ List<CostCategory> get _cats => widget.categories;
|
|
|
+ AppLocalizations get _l10n => widget.l10n;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ super.initState();
|
|
|
+ _cat = _cats.isNotEmpty ? _cats.first.code : 'other';
|
|
|
+ _catLabel = _l10n.get(_cats.firstWhere((c) => c.code == _cat).nameKey);
|
|
|
+ _descCtrl = TextEditingController();
|
|
|
+ _amountCtrl = TextEditingController();
|
|
|
+ _customerCtrl = TextEditingController();
|
|
|
+ _offsetCtrl = TextEditingController();
|
|
|
+ _remarkCtrl = TextEditingController();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ _descCtrl.dispose();
|
|
|
+ _amountCtrl.dispose();
|
|
|
+ _customerCtrl.dispose();
|
|
|
+ _offsetCtrl.dispose();
|
|
|
+ _remarkCtrl.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(
|
|
|
+ _cats.firstWhere((c) => c.code == _cat).nameKey,
|
|
|
+ ),
|
|
|
+ expenseDesc: desc,
|
|
|
+ amount: amount,
|
|
|
+ taxRate: _taxRate,
|
|
|
+ customerVendorName: _customerCtrl.text.trim(),
|
|
|
+ offsetAmount: double.tryParse(_offsetCtrl.text) ?? 0,
|
|
|
+ remark: _remarkCtrl.text.trim(),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ 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<AppColorsExtension>()!;
|
|
|
+ final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
|
|
+
|
|
|
+ return SafeArea(
|
|
|
+ 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: SingleChildScrollView(
|
|
|
+ padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
|
|
+ child: Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
+ children: [
|
|
|
+ _buildCategoryCard(colors),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ _buildDescCard(),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ _buildAmountCard(),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ _buildTaxRateCard(colors),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ // 自动计算展示
|
|
|
+ _buildCalcInfo(colors),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ _buildCustomerCard(),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ _buildOffsetCard(),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ _buildRemarkCard(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<String> labels,
|
|
|
+ required ValueChanged<int> onSelected,
|
|
|
+ required AppColorsExtension colors,
|
|
|
+ }) {
|
|
|
+ final tdTheme = TDTheme.of(context);
|
|
|
+ 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: 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),
|
|
|
+ ),
|
|
|
+ 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),
|
|
|
+ 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);
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── 费用项目 ──
|
|
|
+ Widget _buildDescCard() {
|
|
|
+ final screenWidth = MediaQuery.of(context).size.width;
|
|
|
+ return TDInput(
|
|
|
+ type: TDInputType.cardStyle,
|
|
|
+ cardStyle: TDCardStyle.topText,
|
|
|
+ width: screenWidth - 32,
|
|
|
+ leftLabel: _l10n.get('expenseName'),
|
|
|
+ required: true,
|
|
|
+ controller: _descCtrl,
|
|
|
+ hintText: _l10n.get('enterExpenseName'),
|
|
|
+ contentAlignment: TextAlign.center,
|
|
|
+ showBottomDivider: false,
|
|
|
+ onChanged: (_) => setState(() {}),
|
|
|
+ onClearTap: () {
|
|
|
+ _descCtrl.clear();
|
|
|
+ setState(() {});
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── 金额(含税) ──
|
|
|
+ Widget _buildAmountCard() {
|
|
|
+ final screenWidth = MediaQuery.of(context).size.width;
|
|
|
+ return TDInput(
|
|
|
+ type: TDInputType.cardStyle,
|
|
|
+ cardStyle: TDCardStyle.topText,
|
|
|
+ width: screenWidth - 32,
|
|
|
+ leftLabel: _l10n.get('amountInclTax'),
|
|
|
+ required: true,
|
|
|
+ controller: _amountCtrl,
|
|
|
+ hintText: '>0',
|
|
|
+ contentAlignment: TextAlign.center,
|
|
|
+ inputType: const TextInputType.numberWithOptions(decimal: true),
|
|
|
+ inputFormatters: [
|
|
|
+ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
|
|
|
+ ],
|
|
|
+ showBottomDivider: false,
|
|
|
+ onChanged: (_) => setState(() {}),
|
|
|
+ onClearTap: () {
|
|
|
+ _amountCtrl.clear();
|
|
|
+ setState(() {});
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── 税率 ──
|
|
|
+ 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.primary50,
|
|
|
+ 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 _buildCustomerCard() {
|
|
|
+ final screenWidth = MediaQuery.of(context).size.width;
|
|
|
+ return TDInput(
|
|
|
+ type: TDInputType.cardStyle,
|
|
|
+ cardStyle: TDCardStyle.topText,
|
|
|
+ width: screenWidth - 32,
|
|
|
+ leftLabel: _l10n.get('customerVendor'),
|
|
|
+ controller: _customerCtrl,
|
|
|
+ hintText: _l10n.get('optional'),
|
|
|
+ contentAlignment: TextAlign.center,
|
|
|
+ showBottomDivider: false,
|
|
|
+ onChanged: (_) => setState(() {}),
|
|
|
+ onClearTap: () {
|
|
|
+ _customerCtrl.clear();
|
|
|
+ setState(() {});
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── 已充金额 ──
|
|
|
+ Widget _buildOffsetCard() {
|
|
|
+ final screenWidth = MediaQuery.of(context).size.width;
|
|
|
+ return TDInput(
|
|
|
+ type: TDInputType.cardStyle,
|
|
|
+ cardStyle: TDCardStyle.topText,
|
|
|
+ width: screenWidth - 32,
|
|
|
+ leftLabel: _l10n.get('offsetAmount'),
|
|
|
+ controller: _offsetCtrl,
|
|
|
+ hintText: '0',
|
|
|
+ contentAlignment: TextAlign.center,
|
|
|
+ inputType: const TextInputType.numberWithOptions(decimal: true),
|
|
|
+ inputFormatters: [
|
|
|
+ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
|
|
|
+ ],
|
|
|
+ showBottomDivider: false,
|
|
|
+ onChanged: (_) => setState(() {}),
|
|
|
+ onClearTap: () {
|
|
|
+ _offsetCtrl.clear();
|
|
|
+ setState(() {});
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── 备注 ──
|
|
|
+ Widget _buildRemarkCard(AppColorsExtension colors) {
|
|
|
+ final tdTheme = TDTheme.of(context);
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(left: 4),
|
|
|
+ child: TDText(
|
|
|
+ _l10n.get('detailRemark'),
|
|
|
+ font: tdTheme.fontBodyLarge,
|
|
|
+ fontWeight: FontWeight.w400,
|
|
|
+ style: const TextStyle(letterSpacing: 0),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ TDTextarea(
|
|
|
+ controller: _remarkCtrl,
|
|
|
+ hintText: _l10n.get('optional'),
|
|
|
+ maxLines: 3,
|
|
|
+ minLines: 1,
|
|
|
+ maxLength: 200,
|
|
|
+ indicator: true,
|
|
|
+ padding: EdgeInsets.zero,
|
|
|
+ bordered: true,
|
|
|
+ inputType: TextInputType.multiline,
|
|
|
+ backgroundColor: tdTheme.bgColorContainer,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── 操作按钮 ──
|
|
|
+ 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: _l10n.get('confirmAdd'),
|
|
|
+ size: TDButtonSize.large,
|
|
|
+ type: TDButtonType.fill,
|
|
|
+ shape: TDButtonShape.rectangle,
|
|
|
+ theme: TDButtonTheme.primary,
|
|
|
+ onTap: _confirm,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|