406 lines
15 KiB
Dart
406 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
|
import 'package:url_launcher/url_launcher_string.dart';
|
|
|
|
import '../../../common/config/app_h5_urls.dart';
|
|
import '../../../common/router/app_router.dart';
|
|
import '../../../common/widgets/app_toast.dart';
|
|
import '../../../common/widgets/prototype_ui.dart';
|
|
import '../../auth/application/auth_controller.dart';
|
|
import '../../auth/data/auth_models.dart';
|
|
import '../application/profile_settings_controller.dart';
|
|
import '../data/profile_models.dart';
|
|
|
|
class ProfilePage extends ConsumerWidget {
|
|
const ProfilePage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final settings = ref.watch(profileSettingsControllerProvider);
|
|
final auth = ref.watch(authProvider);
|
|
final cs = ShadTheme.of(context).colorScheme;
|
|
final loggedIn = auth.valueOrNull is LoggedInAuthState;
|
|
final currentUser = auth.valueOrNull is LoggedInAuthState
|
|
? (auth.valueOrNull as LoggedInAuthState).user
|
|
: null;
|
|
|
|
return SafeArea(
|
|
top: false,
|
|
child: ListView(
|
|
padding: const EdgeInsets.fromLTRB(0, 8, 0, 86),
|
|
children: [
|
|
PrototypeFrame(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const Text(
|
|
'我的',
|
|
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600),
|
|
),
|
|
const SizedBox(height: 14),
|
|
PrototypeCard(
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 46,
|
|
height: 46,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF1EFE9),
|
|
borderRadius: BorderRadius.circular(999),
|
|
),
|
|
child: const Icon(
|
|
Icons.person_outline,
|
|
color: Color(0xFF9A968D),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
currentUser?.nickname ?? '未登录',
|
|
style: const TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
currentUser?.phone ?? '登录后可云备份、多端同步',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: cs.mutedForeground,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (!loggedIn)
|
|
FilledButton(
|
|
onPressed: () => context.push(AppRoutes.login),
|
|
style: FilledButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 10,
|
|
),
|
|
minimumSize: Size.zero,
|
|
),
|
|
child: Text('登录'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
PrototypeCard(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
_MenuRow(
|
|
title: '邀请好友,各得奖励',
|
|
subtitle: '需登录',
|
|
icon: Icons.card_giftcard_outlined,
|
|
onTap: () => _showInviteSheet(context),
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
_MenuRow(
|
|
title: '分享我的贵金属估值卡',
|
|
subtitle: '免登录 · 自动打码金额',
|
|
icon: Icons.share_outlined,
|
|
onTap: () =>
|
|
_showShareCardSheet(context, currentUser?.nickname),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
// PrototypeCard(
|
|
// padding: EdgeInsets.zero,
|
|
// child: Column(
|
|
// children: [
|
|
// SettingRow(
|
|
// icon: Icons.download_outlined,
|
|
// title: '本地导出备份',
|
|
// trailing: '导出',
|
|
// onTap: () => _showLocalBackupSheet(context),
|
|
// ),
|
|
// const PrototypeDivider(margin: EdgeInsets.zero),
|
|
// SettingRow(
|
|
// icon: Icons.cloud_outlined,
|
|
// title: '云备份 · 多端同步',
|
|
// trailing: loggedIn ? '已登录' : '登录解锁',
|
|
// onTap: () => _showCloudBackupSheet(context, loggedIn),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// const SizedBox(height: 12),
|
|
PrototypeCard(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
SettingRow(
|
|
icon: Icons.tune_outlined,
|
|
title: '纯度系数管理',
|
|
trailing: '${settings.purityPresets.length} 项',
|
|
onTap: () => _showPuritySheet(context, settings),
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
SettingRow(
|
|
icon: Icons.percent_outlined,
|
|
title: '回收折扣设置',
|
|
trailing:
|
|
'金 ${(settings.recycleDiscounts.values.first * 100).toStringAsFixed(0)}%',
|
|
onTap: () => _showRecycleSheet(context, settings),
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
SettingRow(
|
|
icon: Icons.palette_outlined,
|
|
title: '涨跌颜色',
|
|
trailing: settings.priceColorMode.label,
|
|
onTap: () {
|
|
final nextMode =
|
|
settings.priceColorMode ==
|
|
PriceColorMode.redUpGreenDown
|
|
? PriceColorMode.greenUpRedDown
|
|
: PriceColorMode.redUpGreenDown;
|
|
ref
|
|
.read(profileSettingsControllerProvider.notifier)
|
|
.setPriceColorMode(nextMode);
|
|
_toast(context, '已切换为「${nextMode.label}」');
|
|
},
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
SettingRow(
|
|
icon: Icons.currency_yen_outlined,
|
|
title: '显示币种',
|
|
trailing: '¥ CNY',
|
|
onTap: () => _toast(context, '当前仅支持 ¥ CNY'),
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
SettingRow(
|
|
icon: Icons.grid_view_outlined,
|
|
title: '添加桌面小组件',
|
|
trailing: '占位',
|
|
onTap: () => _showWidgetSheet(context),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
PrototypeCard(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
SettingRow(
|
|
icon: Icons.settings,
|
|
title: '设置',
|
|
trailing: null,
|
|
onTap: () => context.push(AppRoutes.settings),
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
SettingRow(
|
|
icon: Icons.privacy_tip_outlined,
|
|
title: '隐私政策 · 用户协议',
|
|
trailing: null,
|
|
onTap: () => launchUrlString(
|
|
AppH5UrlsHelper.withCacheBuster(
|
|
AppH5Urls.userProtocolUrl,
|
|
),
|
|
mode: LaunchMode.inAppBrowserView,
|
|
),
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
SettingRow(
|
|
icon: Icons.info_outline,
|
|
title: '免责声明 · 数据来源',
|
|
trailing: null,
|
|
onTap: () => _showDisclaimer(context),
|
|
),
|
|
const PrototypeDivider(margin: EdgeInsets.zero),
|
|
SettingRow(
|
|
icon: Icons.feedback_outlined,
|
|
title: '意见反馈 · 关于金值',
|
|
trailing: null,
|
|
onTap: () => _showAbout(context),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 14),
|
|
PrototypeCard(
|
|
child: Text(
|
|
'风险提示 · 数据来源 · 免责\n金值是贵金属资产记录与估值工具,所有信息不构成投资建议。\n· 行情 / 兑换价格均为参考,标注来源与更新时间,实际以交易系统 / 门店为准。\n· 材料价值 = 克重 × 纯度系数 × 上海金现货,非金店零售价(含工费 / 品牌溢价)、非回收实收价。\n· 资讯为信息整理,非投资建议;历史走势不代表未来。',
|
|
style: TextStyle(
|
|
color: cs.mutedForeground,
|
|
height: 1.7,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showDisclaimer(BuildContext context) {
|
|
context.push(AppRoutes.profileDisclaimer);
|
|
}
|
|
|
|
Future<void> _showInviteSheet(BuildContext context) async {
|
|
context.push(AppRoutes.profileInvite);
|
|
}
|
|
|
|
Future<void> _showShareCardSheet(
|
|
BuildContext context,
|
|
String? nickname,
|
|
) async {
|
|
context.push(AppRoutes.profileShare);
|
|
}
|
|
|
|
Future<void> _showLocalBackupSheet(BuildContext context) async {
|
|
context.push(AppRoutes.profileLocalBackup);
|
|
}
|
|
|
|
Future<void> _showCloudBackupSheet(
|
|
BuildContext context,
|
|
bool loggedIn,
|
|
) async {
|
|
context.push(AppRoutes.profileCloudBackup);
|
|
}
|
|
|
|
Future<void> _showWidgetSheet(BuildContext context) async {
|
|
context.push(AppRoutes.profileWidget);
|
|
}
|
|
|
|
Future<void> _showPuritySheet(
|
|
BuildContext context,
|
|
ProfileSettings settings,
|
|
) async {
|
|
context.push(AppRoutes.profilePurity);
|
|
}
|
|
|
|
Future<void> _showRecycleSheet(
|
|
BuildContext context,
|
|
ProfileSettings settings,
|
|
) async {
|
|
context.push(AppRoutes.profileRecycle);
|
|
}
|
|
|
|
void _showAbout(BuildContext context) {
|
|
context.push(AppRoutes.profileAbout);
|
|
}
|
|
}
|
|
|
|
void _toast(BuildContext context, String message) {
|
|
toastInfo(msg: message);
|
|
}
|
|
|
|
class _MenuRow extends StatelessWidget {
|
|
const _MenuRow({
|
|
required this.title,
|
|
required this.subtitle,
|
|
required this.icon,
|
|
required this.onTap,
|
|
});
|
|
|
|
final String title;
|
|
final String subtitle;
|
|
final IconData icon;
|
|
final VoidCallback onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = ShadTheme.of(context).colorScheme;
|
|
return InkWell(
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, size: 20, color: const Color(0xFF8A8780)),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
subtitle,
|
|
style: TextStyle(fontSize: 12, color: cs.mutedForeground),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(Icons.chevron_right, size: 16, color: cs.mutedForeground),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingRow extends StatelessWidget {
|
|
const SettingRow({
|
|
required this.icon,
|
|
required this.title,
|
|
required this.onTap,
|
|
this.trailing,
|
|
this.destructive = false,
|
|
});
|
|
|
|
final IconData icon;
|
|
final String title;
|
|
final String? trailing;
|
|
final VoidCallback onTap;
|
|
final bool destructive;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = ShadTheme.of(context).colorScheme;
|
|
final color = destructive ? cs.destructive : cs.foreground;
|
|
|
|
return InkWell(
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, size: 20, color: const Color(0xFF8A8780)),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: color,
|
|
),
|
|
),
|
|
),
|
|
if (trailing != null)
|
|
Text(
|
|
trailing!,
|
|
style: TextStyle(fontSize: 12, color: cs.mutedForeground),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Icon(Icons.chevron_right, size: 16, color: cs.mutedForeground),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|