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:
parent
003b571f94
commit
b639e8c823
|
|
@ -1,13 +1,30 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../../../../app/theme/app_colors.dart';
|
import '../../../../app/theme/app_colors.dart';
|
||||||
import '../../../../app/theme/app_typography.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});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -16,39 +33,59 @@ class SettingsPage extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
// Account & Security
|
// Account & Security
|
||||||
_buildSection('账号与安全', [
|
_buildSection('账号与安全', [
|
||||||
_buildTile('手机号', subtitle: '138****8888', icon: Icons.phone_rounded),
|
_buildTile('手机号',
|
||||||
_buildTile('邮箱', subtitle: 'u***@email.com', icon: Icons.email_rounded),
|
subtitle: '138****8888', icon: Icons.phone_rounded),
|
||||||
|
_buildTile('邮箱',
|
||||||
|
subtitle: 'u***@email.com', icon: Icons.email_rounded),
|
||||||
_buildTile('修改密码', icon: Icons.lock_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');
|
Navigator.pushNamed(context, '/kyc');
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Payment
|
// Payment
|
||||||
_buildSection('支付管理', [
|
_buildSection('支付管理', [
|
||||||
_buildTile('支付方式', subtitle: 'Visa •••• 4242', icon: Icons.credit_card_rounded),
|
_buildTile('支付方式',
|
||||||
_buildTile('银行账户', subtitle: 'BoA •••• 6789', icon: Icons.account_balance_rounded),
|
subtitle: 'Visa •••• 4242',
|
||||||
|
icon: Icons.credit_card_rounded),
|
||||||
|
_buildTile('银行账户',
|
||||||
|
subtitle: 'BoA •••• 6789',
|
||||||
|
icon: Icons.account_balance_rounded),
|
||||||
_buildTile('支付密码', icon: Icons.password_rounded),
|
_buildTile('支付密码', icon: Icons.password_rounded),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
_buildSection('通知设置', [
|
_buildSection('通知设置', [
|
||||||
_buildSwitchTile('交易通知', true),
|
_buildSwitchTile('交易通知', _notifyTrade,
|
||||||
_buildSwitchTile('到期提醒', true),
|
(v) => setState(() => _notifyTrade = v)),
|
||||||
_buildSwitchTile('行情变动', false),
|
_buildSwitchTile('到期提醒', _notifyExpiry,
|
||||||
_buildSwitchTile('营销推送', false),
|
(v) => setState(() => _notifyExpiry = v)),
|
||||||
|
_buildSwitchTile('行情变动', _notifyMarket,
|
||||||
|
(v) => setState(() => _notifyMarket = v)),
|
||||||
|
_buildSwitchTile('营销推送', _notifyMarketing,
|
||||||
|
(v) => setState(() => _notifyMarketing = v)),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// General
|
// General
|
||||||
_buildSection('通用', [
|
_buildSection('通用', [
|
||||||
_buildTile('语言', subtitle: '简体中文', icon: Icons.language_rounded),
|
_buildTile('语言',
|
||||||
_buildTile('货币', subtitle: 'USD', icon: Icons.attach_money_rounded),
|
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),
|
_buildTile('清除缓存', icon: Icons.cleaning_services_rounded),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// About
|
// About
|
||||||
_buildSection('关于', [
|
_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.description_rounded),
|
||||||
_buildTile('隐私政策', icon: Icons.privacy_tip_rounded),
|
_buildTile('隐私政策', icon: Icons.privacy_tip_rounded),
|
||||||
_buildTile('帮助中心', icon: Icons.help_outline_rounded),
|
_buildTile('帮助中心', icon: Icons.help_outline_rounded),
|
||||||
|
|
@ -59,7 +96,8 @@ class SettingsPage extends StatelessWidget {
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pushNamedAndRemoveUntil('/', (_) => false);
|
Navigator.of(context)
|
||||||
|
.pushNamedAndRemoveUntil('/', (_) => false);
|
||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: AppColors.error,
|
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) {
|
Widget _buildSection(String title, List<Widget> children) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
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(
|
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),
|
title: Text(title, style: AppTypography.bodyMedium),
|
||||||
subtitle: subtitle != null ? Text(subtitle, style: AppTypography.caption) : null,
|
subtitle: subtitle != null
|
||||||
trailing: const Icon(Icons.chevron_right_rounded, size: 20, color: AppColors.textTertiary),
|
? Text(subtitle, style: AppTypography.caption)
|
||||||
|
: null,
|
||||||
|
trailing: const Icon(Icons.chevron_right_rounded,
|
||||||
|
size: 20, color: AppColors.textTertiary),
|
||||||
onTap: onTap ?? () {},
|
onTap: onTap ?? () {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSwitchTile(String title, bool value) {
|
Widget _buildSwitchTile(
|
||||||
|
String title, bool value, ValueChanged<bool> onChanged) {
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
title: Text(title, style: AppTypography.bodyMedium),
|
title: Text(title, style: AppTypography.bodyMedium),
|
||||||
value: value,
|
value: value,
|
||||||
onChanged: (_) {},
|
onChanged: onChanged,
|
||||||
activeColor: AppColors.primary,
|
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: '🇭🇰'),
|
||||||
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue