docs: 删除过时的功能规格文档

feat(ui): 重构资产页面UI,移除shadcn_ui依赖并简化设计

- 删除三个过时的功能规格文档(apply-new-styles.md、bottom-nav-labels.md、theme-dynamic-colors.md)
- 重构充值页面(deposit_page.dart):移除shadcn_ui依赖,简化表单验证和UI设计,使用动态主题颜色
- 重构划转页面(transfer_page.dart):移除复杂动画和shadcn_ui依赖,简化UI布局和交互逻辑
- 重构提现页面(withdraw_page.dart):移除shadcn_ui依赖,简化表单验证和网络选择器
- 重构我的页面相关组件:统一使用动态主题颜色,简化菜单项设计和KYC状态显示
- 所有页面现在使用Theme.of(context)获取动态颜色,支持明暗主题切换
- 移除硬编码的颜色引用,提高代码可维护性和主题一致性
This commit is contained in:
2026-04-08 01:47:51 +08:00
parent 007915b6f2
commit dc6a8afc9a
13 changed files with 1271 additions and 2074 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:provider/provider.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../../core/theme/app_color_scheme.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_theme_extension.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/utils/toast_utils.dart';
import '../../../providers/asset_provider.dart';
import '../../shared/ui_constants.dart';
/// 提現頁面 — 獨立頁面,替代彈窗
/// 提现页面
class WithdrawPage extends StatefulWidget {
final String? balance;
@@ -24,17 +21,16 @@ class _WithdrawPageState extends State<WithdrawPage> {
final _amountController = TextEditingController();
final _addressController = TextEditingController();
final _contactController = TextEditingController();
final _formKey = GlobalKey<ShadFormState>();
final _amountFocus = FocusNode();
bool _isSubmitting = false;
final _feeNotifier = ValueNotifier<String>('提現將扣除 10% 手續費');
final _networksNotifier = ValueNotifier<List<String>>([]);
final _selectedNetworkNotifier = ValueNotifier<String?>(null);
List<String> _networks = [];
String? _selectedNetwork;
@override
void initState() {
super.initState();
_amountController.addListener(_onAmountChanged);
_amountController.addListener(() => setState(() {}));
_loadNetworks();
}
@@ -43,367 +39,468 @@ class _WithdrawPageState extends State<WithdrawPage> {
_amountController.dispose();
_addressController.dispose();
_contactController.dispose();
_feeNotifier.dispose();
_networksNotifier.dispose();
_selectedNetworkNotifier.dispose();
_amountFocus.dispose();
super.dispose();
}
void _onAmountChanged() {
final amount = double.tryParse(_amountController.text) ?? 0;
if (amount > 0) {
final fee = amount * 0.1;
final receivable = amount - fee;
_feeNotifier.value =
'手續費(10%): -${fee.toStringAsFixed(2)} USDT\n應收款: ${receivable.toStringAsFixed(2)} USDT';
} else {
_feeNotifier.value = '提現將扣除 10% 手續費';
}
}
// ============================================
// 业务逻辑
// ============================================
void _loadNetworks() {
context.read<AssetProvider>().getWalletNetworks().then((list) {
if (mounted) {
_networksNotifier.value = list;
if (list.isNotEmpty) {
_selectedNetworkNotifier.value = list.first;
}
setState(() {
_networks = list;
if (list.isNotEmpty) _selectedNetwork = list.first;
});
}
});
}
String get _feeText {
final amount = double.tryParse(_amountController.text) ?? 0;
if (amount > 0) {
final fee = amount * 0.1;
final receivable = amount - fee;
return '手续费(10%): -${fee.toStringAsFixed(2)} USDT | 应收款: ${receivable.toStringAsFixed(2)} USDT';
}
return '提现将扣除 10% 手续费';
}
Future<void> _submitWithdraw() async {
if (!_formKey.currentState!.saveAndValidate()) return;
final amount = double.tryParse(_amountController.text);
if (amount == null || amount <= 0) {
ToastUtils.showError('请输入有效金额');
return;
}
final address = _addressController.text.trim();
if (address.isEmpty) {
ToastUtils.showError('请输入提现地址');
return;
}
if (_isSubmitting) return;
setState(() => _isSubmitting = true);
try {
final response = await context.read<AssetProvider>().withdraw(
amount: _amountController.text,
withdrawAddress: _addressController.text,
withdrawContact:
_contactController.text.isNotEmpty ? _contactController.text : null,
network: _selectedNetworkNotifier.value,
withdrawAddress: address,
withdrawContact: _contactController.text.trim().isNotEmpty
? _contactController.text.trim()
: null,
network: _selectedNetwork,
);
if (!mounted) return;
if (response.success) {
ToastUtils.showSuccess('成功,等待管理員審');
ToastUtils.showSuccess('成功,等待管理员审');
Navigator.of(context).pop(true);
} else {
ToastUtils.showError(response.message ?? '請失敗');
ToastUtils.showError(response.message ?? '请失败');
}
} catch (e) {
if (mounted) ToastUtils.showError('請失敗: $e');
if (mounted) ToastUtils.showError('请失败: $e');
} finally {
if (mounted) setState(() => _isSubmitting = false);
}
}
// ============================================
// 建 UI
// 建 UI
// ============================================
@override
Widget build(BuildContext context) {
final colorScheme = context.colors;
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.background,
backgroundColor: colorScheme.surface,
appBar: AppBar(
leading: IconButton(
icon: const Icon(LucideIcons.arrowLeft, size: 20),
icon: Icon(LucideIcons.arrowLeft,
color: colorScheme.onSurface, size: 20),
onPressed: () => Navigator.of(context).pop(),
),
title: Text(
'',
'',
style: AppTextStyles.headlineLarge(context).copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
backgroundColor: colorScheme.background,
backgroundColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.lg),
padding: const EdgeInsets.fromLTRB(
AppSpacing.md, AppSpacing.xs, AppSpacing.md, AppSpacing.xl),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 餘額顯示
// 可用余额
if (widget.balance != null) ...[
Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'可用餘額',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
Text(
'${widget.balance} USDT',
style: AppTextStyles.headlineMedium(context).copyWith(
fontWeight: FontWeight.bold,
color: context.appColors.up,
),
),
],
),
),
_buildBalanceRow(colorScheme),
const SizedBox(height: AppSpacing.lg),
],
// 表單
ShadForm(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 提現金額
Text(
'提現金額',
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppSpacing.sm),
ShadInputFormField(
id: 'amount',
controller: _amountController,
placeholder: const Text('請輸入提現金額 (USDT)'),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
validator: Validators.amount,
),
const SizedBox(height: AppSpacing.sm),
// 提现金额
_buildSectionLabel(colorScheme, '提现金额'),
const SizedBox(height: AppSpacing.sm),
_buildAmountInput(colorScheme),
const SizedBox(height: AppSpacing.sm),
// 手續費提示
ValueListenableBuilder<String>(
valueListenable: _feeNotifier,
builder: (_, feeText, __) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.info_outline,
size: 14, color: colorScheme.onSurfaceVariant),
const SizedBox(width: AppSpacing.xs),
Expanded(
child: Text(
feeText,
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
],
),
);
},
),
const SizedBox(height: AppSpacing.lg),
// 手续费提示
_buildFeeTip(colorScheme),
const SizedBox(height: AppSpacing.lg),
// 提現網絡
ValueListenableBuilder<List<String>>(
valueListenable: _networksNotifier,
builder: (_, networks, __) {
if (networks.isEmpty) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'提現網絡',
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppSpacing.sm),
ValueListenableBuilder<String?>(
valueListenable: _selectedNetworkNotifier,
builder: (_, selected, __) {
return SizedBox(
width: double.infinity,
child: ShadSelect<String>(
placeholder: const Text('選擇提現網絡'),
initialValue: selected,
selectedOptionBuilder: (context, val) =>
Text(val),
onChanged: (value) {
if (value != null) {
_selectedNetworkNotifier.value = value;
}
},
options: networks
.map((n) =>
ShadOption(value: n, child: Text(n)))
.toList(),
),
);
},
),
const SizedBox(height: AppSpacing.lg),
],
);
},
),
// 提现网络
if (_networks.isNotEmpty) ...[
_buildSectionLabel(colorScheme, '提现网络'),
const SizedBox(height: AppSpacing.sm),
_buildNetworkSelector(colorScheme),
const SizedBox(height: AppSpacing.lg),
],
// 目地址
Text(
'目標地址',
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppSpacing.sm),
ShadInputFormField(
id: 'address',
controller: _addressController,
placeholder: const Text('請輸入提現地址'),
validator: (v) => Validators.required(v, '提現地址'),
),
const SizedBox(height: AppSpacing.lg),
// 目地址
_buildSectionLabel(colorScheme, '目标地址'),
const SizedBox(height: AppSpacing.sm),
_buildAddressInput(colorScheme),
const SizedBox(height: AppSpacing.lg),
// 聯繫方式
Text(
'聯繫方式(可選)',
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: AppSpacing.sm),
ShadInputFormField(
id: 'contact',
controller: _contactController,
placeholder: const Text('方便客服與您聯繫'),
),
],
),
),
// 联系方式
_buildSectionLabel(colorScheme, '联系方式(可选)'),
const SizedBox(height: AppSpacing.sm),
_buildContactInput(colorScheme),
const SizedBox(height: AppSpacing.xl),
// 安全提示
Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColorScheme.warning.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: AppColorScheme.warning.withValues(alpha: 0.15),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.shield_outlined,
size: 16, color: AppColorScheme.warning),
const SizedBox(width: AppSpacing.xs),
Text(
'安全提示',
style: AppTextStyles.labelLarge(context).copyWith(
fontWeight: FontWeight.w600,
color: AppColorScheme.warning,
),
),
],
),
const SizedBox(height: AppSpacing.sm),
Text(
'• 請仔細核對提現地址,地址錯誤將導致資產無法找回\n'
'• 提現申請提交後需等待管理員審批\n'
'• 提現將扣除 10% 手續費',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
height: 1.6,
),
),
],
),
),
_buildSecurityTips(colorScheme),
const SizedBox(height: AppSpacing.xl),
// 提交按
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _isSubmitting ? null : _submitWithdraw,
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
disabledBackgroundColor:
colorScheme.primary.withValues(alpha: 0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.lg),
),
elevation: 0,
),
child: _isSubmitting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: Text(
'提交申請',
style: AppTextStyles.headlineMedium(context).copyWith(
fontWeight: FontWeight.w600,
color: colorScheme.onPrimary,
),
),
),
),
const SizedBox(height: AppSpacing.lg),
// 提交按
_buildSubmitButton(colorScheme),
const SizedBox(height: AppSpacing.md),
// 底部安全保障
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.verified_user_outlined,
size: 14,
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
const SizedBox(width: AppSpacing.xs),
Text(
'資金安全由平台全程保障',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
),
],
),
),
_buildBottomTip(colorScheme),
],
),
),
);
}
// ============================================
// 组件
// ============================================
Widget _buildBalanceRow(ColorScheme colorScheme) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
width: 1,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'可用余额',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
Text(
'${widget.balance} USDT',
style: AppTextStyles.bodyMedium(context).copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
);
}
Widget _buildSectionLabel(ColorScheme colorScheme, String text) {
return Text(
text,
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildAmountInput(ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: 52,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: _amountFocus.hasFocus
? colorScheme.secondary
: colorScheme.outlineVariant.withValues(alpha: 0.5),
width: 1,
),
),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _amountController,
focusNode: _amountFocus,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')),
],
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
decoration: InputDecoration(
hintText: '0.00',
hintStyle: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color:
colorScheme.onSurfaceVariant.withValues(alpha: 0.3),
),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true,
),
),
),
Text(
'USDT',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Widget _buildFeeTip(ColorScheme colorScheme) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(LucideIcons.info,
size: 13, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 6),
Expanded(
child: Text(
_feeText,
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
],
),
);
}
Widget _buildNetworkSelector(ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
width: 1,
),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _selectedNetwork,
isExpanded: true,
hint: Text(
'选择提现网络',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
items: _networks
.map((n) => DropdownMenuItem(
value: n,
child: Text(n),
))
.toList(),
onChanged: (value) {
if (value != null) setState(() => _selectedNetwork = value);
},
),
),
);
}
Widget _buildAddressInput(ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
width: 1,
),
),
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.center,
child: TextField(
controller: _addressController,
style: AppTextStyles.bodyMedium(context),
decoration: InputDecoration(
hintText: '请输入提现地址',
hintStyle: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true,
),
),
);
}
Widget _buildContactInput(ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
width: 1,
),
),
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.center,
child: TextField(
controller: _contactController,
style: AppTextStyles.bodyMedium(context),
decoration: InputDecoration(
hintText: '方便客服与您联系',
hintStyle: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true,
),
),
);
}
Widget _buildSecurityTips(ColorScheme colorScheme) {
final tips = [
'请仔细核对提现地址,地址错误将导致资产无法找回',
'提现申请提交后需等待管理员审批',
'提现将扣除 10% 手续费',
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: tips
.map((t) => Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
Expanded(
child: Text(
t,
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
],
),
))
.toList(),
);
}
Widget _buildSubmitButton(ColorScheme colorScheme) {
return SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _isSubmitting ? null : _submitWithdraw,
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.onSurface,
foregroundColor: colorScheme.surface,
disabledBackgroundColor: colorScheme.surfaceContainerHighest,
disabledForegroundColor: colorScheme.onSurfaceVariant,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
child: _isSubmitting
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
colorScheme.onSurfaceVariant,
),
),
)
: Text(
'提交申请',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
),
);
}
Widget _buildBottomTip(ColorScheme colorScheme) {
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(LucideIcons.shieldCheck,
size: 13, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 4),
Text(
'资金安全由平台全程保障',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
);
}
}

View File

@@ -1,34 +1,29 @@
import 'package:flutter/material.dart';
import '../../../../core/theme/app_color_scheme.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme.dart';
/// 退出登錄按鈕
/// 退出登录按钮
class LogoutButton extends StatelessWidget {
final VoidCallback onLogout;
const LogoutButton({super.key, required this.onLogout});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onLogout,
child: Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: AppColorScheme.down.withOpacity(0.05),
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(
color: AppColorScheme.down.withOpacity(0.15),
final colorScheme = Theme.of(context).colorScheme;
return SizedBox(
width: double.infinity,
height: 48,
child: OutlinedButton(
onPressed: onLogout,
style: OutlinedButton.styleFrom(
foregroundColor: colorScheme.error,
side: BorderSide(color: colorScheme.error),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
child: Center(
child: Text(
'退出登錄',
style: AppTextStyles.headlineMedium(context).copyWith(
color: AppColorScheme.down,
),
),
child: const Text(
'退出登錄',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
),
);

View File

@@ -1,16 +1,12 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../../../core/theme/app_color_scheme.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../kyc_page.dart';
import '../welfare_center_page.dart';
import 'menu_group_container.dart';
import 'menu_row.dart';
import 'menu_trailing_widgets.dart';
/// 菜單分組1 - 福利中心 / 實名認證 / 安全置 / 消息通知
/// 菜单分组1 - 福利中心 / 实名认证 / 安全置 / 消息通知
class MenuGroup1 extends StatelessWidget {
final int kycStatus;
final void Function(String) onShowComingSoon;
@@ -23,13 +19,14 @@ class MenuGroup1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return MenuGroupContainer(
child: Column(
children: [
// 福利中心
MenuRow(
icon: LucideIcons.gift,
iconColor: AppColorScheme.darkSecondary, // gold
iconColor: colorScheme.secondary,
title: '福利中心',
onTap: () {
Navigator.push(
@@ -39,15 +36,14 @@ class MenuGroup1 extends StatelessWidget {
},
),
const Divider(height: 1),
// 實名認證
MenuRow(
icon: LucideIcons.shieldCheck,
iconColor: context.appColors.up,
iconColor: colorScheme.secondary,
title: '實名認證',
trailing: KycBadge(kycStatus: kycStatus),
onTap: () {
if (kycStatus == 2) {
showKycStatusDialog(context);
_showKycStatusDialog(context);
} else {
Navigator.push(
context,
@@ -57,18 +53,16 @@ class MenuGroup1 extends StatelessWidget {
},
),
const Divider(height: 1),
// 安全設置
MenuRow(
icon: LucideIcons.lock,
iconColor: context.colors.onSurfaceVariant,
iconColor: colorScheme.onSurfaceVariant,
title: '安全設置',
onTap: () => onShowComingSoon('安全設置'),
),
const Divider(height: 1),
// 消息通知
MenuRow(
icon: LucideIcons.bell,
iconColor: context.colors.onSurfaceVariant,
iconColor: colorScheme.onSurfaceVariant,
title: '消息通知',
trailing: const RedDotIndicator(),
onTap: () => onShowComingSoon('消息通知'),
@@ -79,23 +73,16 @@ class MenuGroup1 extends StatelessWidget {
}
}
/// 顯示 KYC 認證狀態對話框
void showKycStatusDialog(BuildContext context) {
showShadDialog(
void _showKycStatusDialog(BuildContext context) {
showDialog(
context: context,
builder: (ctx) => ShadDialog.alert(
title: Row(
children: [
Icon(Icons.check_circle, color: AppColorScheme.up, size: 20),
SizedBox(width: AppSpacing.sm),
const Text('實名認證'),
],
),
description: const Text('您的實名認證已通過'),
builder: (ctx) => AlertDialog(
title: const Text('實名認證'),
content: const Text('您的實名認證已通過'),
actions: [
ShadButton(
child: const Text('確定'),
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('确定'),
),
],
),

View File

@@ -1,11 +1,6 @@
import 'package:flutter/material.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme_extension.dart';
/// 菜單分組容器 - 統一的圓角卡片樣式
///
/// 所有菜單分組共享相同的容器樣式:背景色、圓角、邊框。
/// 通過 [child] 傳入菜單項 Column。
/// 菜单分组容器
class MenuGroupContainer extends StatelessWidget {
final Widget child;
@@ -13,14 +8,16 @@ class MenuGroupContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: context.appColors.surfaceCard,
borderRadius: BorderRadius.circular(AppRadius.lg),
color: colorScheme.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: context.appColors.ghostBorder,
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
),
),
child: child,

View File

@@ -1,12 +1,8 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/theme/app_theme_extension.dart';
/// 行菜單項:圖標 + 標題 + 尾部組件 (chevron)
///
/// 圖標顏色 (通常是使用主題色)
/// 行菜单项:图标 + 标题 + 尾部
class MenuRow extends StatelessWidget {
final IconData icon;
final Color iconColor;
@@ -25,38 +21,30 @@ class MenuRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
children: [
// Icon in 36x36 rounded container
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: context.appColors.surfaceCardHigh,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Icon(icon, size: 18, color: iconColor),
),
),
Icon(icon, size: 18, color: iconColor),
const SizedBox(width: 10),
// Title
Expanded(
child: Text(
title,
style: AppTextStyles.headlineMedium(context),
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w500,
),
),
),
// Trailing
if (trailing != null)
if (trailing != null) trailing!,
if (trailing == null)
Icon(
LucideIcons.chevronRight,
size: 16,
color: context.colors.onSurfaceVariant,
color: colorScheme.onSurfaceVariant,
),
],
),

View File

@@ -1,178 +1,96 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:provider/provider.dart';
import '../../../../core/theme/app_color_scheme.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../../../../providers/theme_provider.dart';
/// KYC 狀態徽章 (e.g. "已認證" green badge + chevron)
///
/// 根據 [kycStatus] 顯示不同狀態:
/// - 2: 已認證(綠色)
/// - 1: 審核中(橙色)
/// - 其他: 僅顯示 chevron
/// KYC 状态文字
class KycBadge extends StatelessWidget {
final int kycStatus;
const KycBadge({super.key, required this.kycStatus});
@override
Widget build(BuildContext context) {
final green = context.appColors.up;
final colorScheme = Theme.of(context).colorScheme;
if (kycStatus == 2) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: green.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'已認證',
style: AppTextStyles.labelMedium(context).copyWith(
color: green,
),
),
),
const SizedBox(width: 8),
Icon(
LucideIcons.chevronRight,
size: 16,
color: context.colors.onSurfaceVariant,
),
],
return Text(
'已認證',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.secondary,
fontWeight: FontWeight.w500,
),
);
}
if (kycStatus == 1) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColorScheme.warning.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'審核中',
style: AppTextStyles.labelMedium(context).copyWith(
color: AppColorScheme.warning,
),
),
),
const SizedBox(width: 8),
Icon(
LucideIcons.chevronRight,
size: 16,
color: context.colors.onSurfaceVariant,
),
],
return Text(
'審核中',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.tertiary,
fontWeight: FontWeight.w500,
),
);
}
return Icon(
LucideIcons.chevronRight,
size: 16,
color: context.colors.onSurfaceVariant,
return Text(
'未認證',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
);
}
}
/// 紅點指示器 - 消息通知 + chevron
/// 红点指示器
class RedDotIndicator extends StatelessWidget {
const RedDotIndicator({super.key});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: AppColorScheme.down,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Icon(
LucideIcons.chevronRight,
size: 16,
color: context.colors.onSurfaceVariant,
),
],
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: colorScheme.error,
shape: BoxShape.circle,
),
);
}
}
/// 深色模式切
/// 深色模式切
class DarkModeRow extends StatelessWidget {
const DarkModeRow({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final themeProvider = context.watch<ThemeProvider>();
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
children: [
// Icon in 36x36 rounded container
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: context.appColors.surfaceCardHigh,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Icon(
LucideIcons.moon,
size: 18,
color: context.colors.onSurfaceVariant,
),
),
Icon(
LucideIcons.moon,
size: 18,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 10),
Expanded(
child: Text(
'深色模式',
style: AppTextStyles.headlineMedium(context),
),
),
// Toggle switch - matching .pen design (44x24 rounded pill)
GestureDetector(
onTap: () => themeProvider.toggleTheme(),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 44,
height: 24,
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: context.appColors.surfaceCardHigh,
borderRadius: BorderRadius.circular(12),
),
child: AnimatedAlign(
duration: const Duration(milliseconds: 200),
alignment:
themeProvider.isDarkMode
? Alignment.centerRight : Alignment.centerLeft,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: context.colors.onSurface,
shape: BoxShape.circle,
),
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w500,
),
),
),
),
Switch(
value: themeProvider.isDarkMode,
onChanged: (_) => themeProvider.toggleTheme(),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
],
),

View File

@@ -1,60 +1,59 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/theme/app_theme_extension.dart';
import 'avatar_circle.dart';
/// 用戶資料卡片 - 頭像 + 用戶名 + 徽章 + chevron
/// 用户资料卡片
class ProfileCard extends StatelessWidget {
final dynamic user;
const ProfileCard({super.key, required this.user});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: context.appColors.surfaceCard,
borderRadius: BorderRadius.circular(AppRadius.lg),
color: colorScheme.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: context.appColors.ghostBorder,
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
),
),
child: Row(
children: [
// Avatar
AvatarCircle(
radius: 24,
fontSize: 18,
radius: 22,
fontSize: 16,
text: user?.avatarText,
),
const SizedBox(width: 12),
// Name + badge column
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.username ?? '未登錄',
style: AppTextStyles.headlineLarge(context),
style: AppTextStyles.bodyLarge(context).copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
const SizedBox(height: 2),
Text(
'普通用戶',
style: AppTextStyles.bodyMedium(context).copyWith(
fontWeight: FontWeight.normal,
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
// Chevron
Icon(
LucideIcons.chevronRight,
size: 16,
color: context.colors.onSurfaceVariant,
color: colorScheme.onSurfaceVariant,
),
],
),

View File

@@ -1,19 +1,16 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:provider/provider.dart';
import '../../../core/theme/app_color_scheme.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../providers/auth_provider.dart';
import '../auth/login_page.dart';
import 'components/about_dialog_helpers.dart';
import 'components/avatar_circle.dart';
import 'components/logout_button.dart';
import 'components/menu_group1.dart';
import 'components/menu_group2.dart';
import 'components/profile_card.dart';
/// 我的頁面 - 匹配 .pen 設計稿
/// 我的页面
class MinePage extends StatefulWidget {
const MinePage({super.key});
@@ -32,7 +29,7 @@ class _MinePageState extends State<MinePage>
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.background,
backgroundColor: colorScheme.surface,
body: Consumer<AuthProvider>(
builder: (context, auth, _) {
return SingleChildScrollView(
@@ -55,12 +52,6 @@ class _MinePageState extends State<MinePage>
SizedBox(height: AppSpacing.lg),
LogoutButton(onLogout: () => _handleLogout(auth)),
SizedBox(height: AppSpacing.md),
Text(
'System Build v1.0.0',
style: AppTextStyles.bodySmall(context).copyWith(
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
),
),
],
),
);
@@ -70,21 +61,15 @@ class _MinePageState extends State<MinePage>
}
void _showComingSoon(String feature) {
showShadDialog(
showDialog(
context: context,
builder: (context) => ShadDialog.alert(
title: Row(
children: [
Icon(Icons.construction, color: AppColorScheme.warning, size: 20),
SizedBox(width: AppSpacing.sm),
const Text('功能開發中'),
],
),
description: Text('$feature功能正在開發中,敬請期待~'),
builder: (ctx) => AlertDialog(
title: const Text('功能開發中'),
content: Text('$feature功能正在開發中,敬請期待'),
actions: [
ShadButton(
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('知道了'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
@@ -92,37 +77,29 @@ class _MinePageState extends State<MinePage>
}
void _showAboutDialog() {
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
showDialog(
context: context,
builder: (context) => ShadDialog(
builder: (ctx) => AlertDialog(
title: Row(
children: [
AvatarCircle(radius: 20, fontSize: 16),
SizedBox(width: AppSpacing.sm + AppSpacing.xs),
AvatarCircle(radius: 16, fontSize: 12),
const SizedBox(width: 8),
const Text('模擬所'),
],
),
child: Column(
content: const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'虛擬貨幣模擬交易平臺',
style: TextStyle(color: colorScheme.onSurfaceVariant),
),
SizedBox(height: AppSpacing.md),
InfoRow(icon: Icons.code, text: '版本: 1.0.0'),
SizedBox(height: AppSpacing.sm),
InfoRow(
icon: Icons.favorite,
text: 'Built with Flutter & Material Design 3'),
Text('虛擬貨幣模擬交易平臺'),
SizedBox(height: 8),
Text('版本: 1.0.0'),
],
),
actions: [
ShadButton(
child: const Text('確定'),
onPressed: () => Navigator.of(context).pop(),
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('确定'),
),
],
),
@@ -130,18 +107,19 @@ class _MinePageState extends State<MinePage>
}
void _handleLogout(AuthProvider auth) {
showShadDialog(
final colorScheme = Theme.of(context).colorScheme;
showDialog(
context: context,
builder: (ctx) => ShadDialog.alert(
title: const Text('確認退出'),
description: const Text('確定要退出登錄嗎?'),
builder: (ctx) => AlertDialog(
title: const Text('退出登錄'),
content: const Text('確定要退出登錄嗎?'),
actions: [
ShadButton.outline(
child: const Text('取消'),
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text('取消',
style: TextStyle(color: colorScheme.onSurfaceVariant)),
),
ShadButton.destructive(
child: const Text('退出'),
TextButton(
onPressed: () async {
Navigator.of(ctx).pop();
await auth.logout();
@@ -152,6 +130,8 @@ class _MinePageState extends State<MinePage>
);
}
},
child: Text('退出', style: TextStyle(color: colorScheme.error)),
),
],
),