feat: 设置页面新增货币选择器和语言选择器

- 设置页面从StatelessWidget改为StatefulWidget
- 新增货币选择器bottom sheet,支持USD/CNY/EUR/GBP/JPY/HKD
- 新增语言选择器bottom sheet,支持简体中文/繁體中文/English/日本語
- 通知开关改为可交互状态
- 货币subtitle动态显示已选货币代码和符号

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-11 21:18:11 -08:00
parent 003b571f94
commit b639e8c823
1 changed files with 238 additions and 21 deletions

View File

@ -1,13 +1,30 @@
import 'package:flutter/material.dart';
import '../../../../app/theme/app_colors.dart';
import '../../../../app/theme/app_typography.dart';
import '../../../../app/theme/app_spacing.dart';
///
///
///
class SettingsPage extends StatelessWidget {
///
///
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
// (/)
_CurrencyOption _selectedCurrency = _currencyOptions[0]; // USD
String _selectedLanguage = '简体中文';
//
bool _notifyTrade = true;
bool _notifyExpiry = true;
bool _notifyMarket = false;
bool _notifyMarketing = false;
@override
Widget build(BuildContext context) {
return Scaffold(
@ -16,39 +33,59 @@ class SettingsPage extends StatelessWidget {
children: [
// Account & Security
_buildSection('账号与安全', [
_buildTile('手机号', subtitle: '138****8888', icon: Icons.phone_rounded),
_buildTile('邮箱', subtitle: 'u***@email.com', icon: Icons.email_rounded),
_buildTile('手机号',
subtitle: '138****8888', icon: Icons.phone_rounded),
_buildTile('邮箱',
subtitle: 'u***@email.com', icon: Icons.email_rounded),
_buildTile('修改密码', icon: Icons.lock_rounded),
_buildTile('身份认证', subtitle: 'L1 基础认证', icon: Icons.verified_user_rounded, onTap: () {
_buildTile('身份认证',
subtitle: 'L1 基础认证',
icon: Icons.verified_user_rounded, onTap: () {
Navigator.pushNamed(context, '/kyc');
}),
]),
// Payment
_buildSection('支付管理', [
_buildTile('支付方式', subtitle: 'Visa •••• 4242', icon: Icons.credit_card_rounded),
_buildTile('银行账户', subtitle: 'BoA •••• 6789', icon: Icons.account_balance_rounded),
_buildTile('支付方式',
subtitle: 'Visa •••• 4242',
icon: Icons.credit_card_rounded),
_buildTile('银行账户',
subtitle: 'BoA •••• 6789',
icon: Icons.account_balance_rounded),
_buildTile('支付密码', icon: Icons.password_rounded),
]),
// Notifications
_buildSection('通知设置', [
_buildSwitchTile('交易通知', true),
_buildSwitchTile('到期提醒', true),
_buildSwitchTile('行情变动', false),
_buildSwitchTile('营销推送', false),
_buildSwitchTile('交易通知', _notifyTrade,
(v) => setState(() => _notifyTrade = v)),
_buildSwitchTile('到期提醒', _notifyExpiry,
(v) => setState(() => _notifyExpiry = v)),
_buildSwitchTile('行情变动', _notifyMarket,
(v) => setState(() => _notifyMarket = v)),
_buildSwitchTile('营销推送', _notifyMarketing,
(v) => setState(() => _notifyMarketing = v)),
]),
// General
_buildSection('通用', [
_buildTile('语言', subtitle: '简体中文', icon: Icons.language_rounded),
_buildTile('货币', subtitle: 'USD', icon: Icons.attach_money_rounded),
_buildTile('语言',
subtitle: _selectedLanguage,
icon: Icons.language_rounded,
onTap: () => _showLanguagePicker(context)),
_buildTile('货币',
subtitle:
'${_selectedCurrency.code} (${_selectedCurrency.symbol})',
icon: Icons.attach_money_rounded,
onTap: () => _showCurrencyPicker(context)),
_buildTile('清除缓存', icon: Icons.cleaning_services_rounded),
]),
// About
_buildSection('关于', [
_buildTile('版本', subtitle: 'v1.0.0', icon: Icons.info_outline_rounded),
_buildTile('版本',
subtitle: 'v1.0.0', icon: Icons.info_outline_rounded),
_buildTile('用户协议', icon: Icons.description_rounded),
_buildTile('隐私政策', icon: Icons.privacy_tip_rounded),
_buildTile('帮助中心', icon: Icons.help_outline_rounded),
@ -59,7 +96,8 @@ class SettingsPage extends StatelessWidget {
padding: const EdgeInsets.all(20),
child: OutlinedButton(
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil('/', (_) => false);
Navigator.of(context)
.pushNamedAndRemoveUntil('/', (_) => false);
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.error,
@ -74,6 +112,152 @@ class SettingsPage extends StatelessWidget {
);
}
// ============================================================
// Currency Picker
// ============================================================
void _showCurrencyPicker(BuildContext context) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (_) => Container(
decoration: const BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Handle
Container(
margin: const EdgeInsets.only(top: 12),
width: 36,
height: 4,
decoration: BoxDecoration(
color: AppColors.gray200,
borderRadius: AppSpacing.borderRadiusFull,
),
),
// Title
Padding(
padding: const EdgeInsets.all(16),
child: Text('选择计价货币', style: AppTypography.h3),
),
const Divider(height: 1),
// Currency list
..._currencyOptions.map((option) {
final isSelected = _selectedCurrency.code == option.code;
return ListTile(
leading: Text(option.flag, style: const TextStyle(fontSize: 24)),
title: Text(
'${option.name} (${option.code})',
style: AppTypography.bodyMedium.copyWith(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected
? AppColors.primary
: AppColors.textPrimary,
),
),
subtitle: Text(
'符号: ${option.symbol}',
style: AppTypography.caption,
),
trailing: isSelected
? const Icon(Icons.check_circle_rounded,
color: AppColors.primary, size: 22)
: null,
onTap: () {
setState(() => _selectedCurrency = option);
Navigator.pop(context);
},
);
}),
// Note
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
child: Text(
'此设置影响交易页面中所有价格的计价货币显示',
style: AppTypography.caption.copyWith(
color: AppColors.textTertiary,
),
textAlign: TextAlign.center,
),
),
],
),
),
);
}
// ============================================================
// Language Picker
// ============================================================
void _showLanguagePicker(BuildContext context) {
final languages = [
('简体中文', 'zh-CN', '🇨🇳'),
('繁體中文', 'zh-TW', '🇹🇼'),
('English', 'en', '🇺🇸'),
('日本語', 'ja', '🇯🇵'),
];
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (_) => Container(
decoration: const BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.only(top: 12),
width: 36,
height: 4,
decoration: BoxDecoration(
color: AppColors.gray200,
borderRadius: AppSpacing.borderRadiusFull,
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Text('选择语言', style: AppTypography.h3),
),
const Divider(height: 1),
...languages.map((lang) {
final (name, _, flag) = lang;
final isSelected = _selectedLanguage == name;
return ListTile(
leading: Text(flag, style: const TextStyle(fontSize: 24)),
title: Text(
name,
style: AppTypography.bodyMedium.copyWith(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
color: isSelected
? AppColors.primary
: AppColors.textPrimary,
),
),
trailing: isSelected
? const Icon(Icons.check_circle_rounded,
color: AppColors.primary, size: 22)
: null,
onTap: () {
setState(() => _selectedLanguage = name);
Navigator.pop(context);
},
);
}),
const SizedBox(height: 24),
],
),
),
);
}
// ============================================================
// Shared Builders
// ============================================================
Widget _buildSection(String title, List<Widget> children) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -90,22 +274,55 @@ class SettingsPage extends StatelessWidget {
);
}
Widget _buildTile(String title, {String? subtitle, IconData? icon, VoidCallback? onTap}) {
Widget _buildTile(String title,
{String? subtitle, IconData? icon, VoidCallback? onTap}) {
return ListTile(
leading: icon != null ? Icon(icon, size: 22, color: AppColors.textSecondary) : null,
leading: icon != null
? Icon(icon, size: 22, color: AppColors.textSecondary)
: null,
title: Text(title, style: AppTypography.bodyMedium),
subtitle: subtitle != null ? Text(subtitle, style: AppTypography.caption) : null,
trailing: const Icon(Icons.chevron_right_rounded, size: 20, color: AppColors.textTertiary),
subtitle: subtitle != null
? Text(subtitle, style: AppTypography.caption)
: null,
trailing: const Icon(Icons.chevron_right_rounded,
size: 20, color: AppColors.textTertiary),
onTap: onTap ?? () {},
);
}
Widget _buildSwitchTile(String title, bool value) {
Widget _buildSwitchTile(
String title, bool value, ValueChanged<bool> onChanged) {
return SwitchListTile(
title: Text(title, style: AppTypography.bodyMedium),
value: value,
onChanged: (_) {},
onChanged: onChanged,
activeColor: AppColors.primary,
);
}
}
// ============================================================
// Currency Options
// ============================================================
class _CurrencyOption {
final String code;
final String symbol;
final String name;
final String flag;
const _CurrencyOption({
required this.code,
required this.symbol,
required this.name,
required this.flag,
});
}
const _currencyOptions = [
_CurrencyOption(code: 'USD', symbol: '\$', name: '美元', flag: '🇺🇸'),
_CurrencyOption(code: 'CNY', symbol: '¥', name: '人民币', flag: '🇨🇳'),
_CurrencyOption(code: 'EUR', symbol: '', name: '欧元', flag: '🇪🇺'),
_CurrencyOption(code: 'GBP', symbol: '£', name: '英镑', flag: '🇬🇧'),
_CurrencyOption(code: 'JPY', symbol: '¥', name: '日元', flag: '🇯🇵'),
_CurrencyOption(code: 'HKD', symbol: 'HK\$', name: '港币', flag: '🇭🇰'),
];