Files
jinzhi/lib/features/profile/presentation/profile_page.dart
T
2026-06-23 15:05:03 +08:00

411 lines
15 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_remix/flutter_remix.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, 20),
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);
toastInfo(msg: '已切换为「${nextMode.label}」');
},
),
const PrototypeDivider(margin: EdgeInsets.zero),
SettingRow(
icon: Icons.currency_yen_outlined,
title: '显示币种',
trailing: '¥ CNY',
onTap: () => toastInfo(msg: '当前仅支持 ¥ 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: FlutterRemix.file_list_3_line,
title: '用户协议',
onTap: () => launchUrlString(
AppH5UrlsHelper.withCacheBuster(
AppH5Urls.userProtocolUrl,
),
mode: LaunchMode.inAppBrowserView,
),
),
const PrototypeDivider(margin: EdgeInsets.zero),
SettingRow(
icon: FlutterRemix.shield_check_line,
title: '隐私政策',
onTap: () => launchUrlString(
AppH5UrlsHelper.withCacheBuster(AppH5Urls.privacyUrl),
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);
}
}
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),
],
),
),
);
}
}