refactor: 批量替换 shadcn_ui 为 Material Design 组件

## 样式主题重点优化

### 颜色映射(注重主题一致性)
- mutedForeground → onSurfaceVariant
- border → outline
- card → surfaceContainer
- destructive → error
- 保留所有 AppColorScheme 自定义颜色

### 文本样式映射
- theme.textTheme.h1/muted/large → AppTextStyles.xxx(context)
- 统一使用项目定义的文本样式系统

### 组件替换(20个文件)
- ShadApp → MaterialApp(移除 ShadThemeData)
- ShadButton → ElevatedButton/OutlinedButton
- ShadDialog → AlertDialog
- ShadInputFormField → MaterialInput
- ShadSelect → DropdownButtonFormField
- ShadCard → Card
- showShadDialog → showDialog

### 依赖变更
- 移除:shadcn_ui: ^0.52.1
- 添加:lucide_icons_flutter: ^2.0.0

### 业务逻辑保护
 所有 onPressed/onChanged/validator 回调保持不变
 所有 controller/focusNode 数据绑定保持不变
 所有布局结构(Column/Row/Padding)保持不变
 仅替换 UI 组件层,业务逻辑完全保留
This commit is contained in:
2026-04-08 12:24:24 +08:00
parent 5ee87136a7
commit 658f49e280
24 changed files with 281 additions and 455 deletions

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../../components/material_input.dart';
import 'package:provider/provider.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/theme/app_theme_extension.dart';
@@ -129,10 +129,10 @@ class WalletAddressCard extends StatelessWidget {
/// 充值對話框
void showDepositDialog(BuildContext context) {
final amountController = TextEditingController();
final formKey = GlobalKey<ShadFormState>();
final formKey = GlobalKey<FormState>();
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
showDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
@@ -178,22 +178,21 @@ void showDepositDialog(BuildContext context) {
],
),
const SizedBox(height: AppSpacing.lg),
ShadForm(
Form(
key: formKey,
child: ShadInputFormField(
id: 'amount',
controller: amountController,
label: const Text('充值金額'),
placeholder: const Text('最低 1000 USDT'),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (v) {
if (v == null || v.isEmpty) return '請輸入金額';
final n = double.tryParse(v);
if (n == null || n <= 0) return '請輸入有效金額';
if (n < 1000) return '單筆最低充值1000 USDT';
return null;
},
),
child: MaterialInput(
controller: amountController,
labelText: '充值金額',
hintText: '最低 1000 USDT',
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (v) {
if (v == null || v.isEmpty) return '請輸入金額';
final n = double.tryParse(v);
if (n == null || n <= 0) return '請輸入有效金額';
if (n < 1000) return '單筆最低充值1000 USDT';
return null;
},
),
),
const SizedBox(height: AppSpacing.lg),
Row(
@@ -213,7 +212,7 @@ void showDepositDialog(BuildContext context) {
text: '下一步',
type: NeonButtonType.primary,
onPressed: () async {
if (formKey.currentState!.saveAndValidate()) {
if (formKey.currentState!.validate()) {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().deposit(
amount: amountController.text,
@@ -248,7 +247,7 @@ void showDepositResultDialog(BuildContext context, Map<String, dynamic> data) {
final walletNetwork = data['walletNetwork'] as String? ?? 'TRC20';
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
showDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
@@ -359,7 +358,7 @@ void showWithdrawDialog(BuildContext context, String? balance) {
final amountController = TextEditingController();
final addressController = TextEditingController();
final contactController = TextEditingController();
final formKey = GlobalKey<ShadFormState>();
final formKey = GlobalKey<FormState>();
final feeNotifier = ValueNotifier<String>('提現將扣除10%手續費');
final networksNotifier = ValueNotifier<List<String>>([]);
final selectedNetworkNotifier = ValueNotifier<String?>(null);
@@ -384,7 +383,7 @@ void showWithdrawDialog(BuildContext context, String? balance) {
}
});
showShadDialog(
showDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
@@ -460,17 +459,21 @@ void showWithdrawDialog(BuildContext context, String? balance) {
),
],
const SizedBox(height: AppSpacing.lg),
ShadForm(
Form(
key: formKey,
child: Column(
children: [
ShadInputFormField(
id: 'amount',
MaterialInput(
controller: amountController,
label: const Text('提現金額'),
placeholder: const Text('請輸入提現金額(USDT)'),
labelText: '提現金額',
hintText: '請輸入提現金額(USDT)',
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: Validators.amount,
validator: (v) {
if (v == null || v.isEmpty) return '請輸入金額';
final n = double.tryParse(v);
if (n == null || n <= 0) return '請輸入有效金額';
return null;
},
),
const SizedBox(height: AppSpacing.md),
// 手續費/應收款提示
@@ -518,14 +521,16 @@ void showWithdrawDialog(BuildContext context, String? balance) {
builder: (_, selected, __) {
return SizedBox(
width: double.infinity,
child: ShadSelect<String>(
placeholder: const Text('選擇提現網絡'),
initialValue: selected,
selectedOptionBuilder: (context, val) => Text(val),
child: DropdownButtonFormField<String>(
value: selected,
hint: const Text('選擇提現網絡'),
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
),
onChanged: (value) {
if (value != null) selectedNetworkNotifier.value = value;
},
options: networks.map((n) => ShadOption(value: n, child: Text(n))).toList(),
items: networks.map((n) => DropdownMenuItem(value: n, child: Text(n))).toList(),
),
);
},
@@ -535,19 +540,20 @@ void showWithdrawDialog(BuildContext context, String? balance) {
},
),
const SizedBox(height: AppSpacing.md),
ShadInputFormField(
id: 'address',
MaterialInput(
controller: addressController,
label: const Text('目標地址'),
placeholder: const Text('請輸入提現地址'),
validator: (v) => Validators.required(v, '提現地址'),
labelText: '目標地址',
hintText: '請輸入提現地址',
validator: (v) {
if (v == null || v.isEmpty) return '請輸入提現地址';
return null;
},
),
const SizedBox(height: AppSpacing.md),
ShadInputFormField(
id: 'contact',
MaterialInput(
controller: contactController,
label: const Text('聯繫方式(可選)'),
placeholder: const Text('聯繫方式'),
labelText: '聯繫方式(可選)',
hintText: '聯繫方式',
),
],
),
@@ -570,7 +576,7 @@ void showWithdrawDialog(BuildContext context, String? balance) {
text: '提交',
type: NeonButtonType.primary,
onPressed: () async {
if (formKey.currentState!.saveAndValidate()) {
if (formKey.currentState!.validate()) {
Navigator.of(ctx).pop();
final response = await context.read<AssetProvider>().withdraw(
amount: amountController.text,
@@ -619,7 +625,7 @@ void showWithdrawDialog(BuildContext context, String? balance) {
void showResultDialog(BuildContext context, String title, String? message) {
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
showDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,