Files
jinzhi/lib/features/profile/presentation/profile_action_pages.dart
T
2026-06-23 11:07:12 +08:00

1544 lines
47 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'dart:ui' as ui;
import '../../../common/domain/metal_type.dart';
import '../../../common/domain/money.dart';
import '../../../common/widgets/app_toast.dart';
import '../../../common/router/app_router.dart';
import '../../../common/util/share_util.dart';
import '../../../common/widgets/adaptive.dart';
import '../../../common/widgets/circle_back_button.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 InviteRewardPage extends StatelessWidget {
const InviteRewardPage({super.key});
@override
Widget build(BuildContext context) {
return _ActionScaffold(
title: '邀请好友',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _InviteCodeCard(),
const SizedBox(height: 16),
const PrototypeCard(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Column(
children: [
_KeyValueLine(label: '已邀请好友', value: '3 人'),
PrototypeDivider(margin: EdgeInsets.zero),
_KeyValueLine(
label: '累计获得奖励',
value: '¥120',
valueColor: Color(0xFFB5862A),
),
],
),
),
const SizedBox(height: 14),
FilledButton(
onPressed: () => toastInfo(msg: '演示:拉起微信 / 系统分享面板'),
child: const Text('分享给好友'),
),
const SizedBox(height: 14),
const Text(
'好友通过你的邀请码注册并完成首次记账,双方各得奖励。奖励权益将随产品逐步开放,具体以活动规则为准。',
style: TextStyle(
fontSize: 11,
height: 1.6,
color: Color(0xFFB3AFA5),
),
),
],
),
);
}
}
class ShareCardPage extends ConsumerStatefulWidget {
const ShareCardPage({super.key});
@override
ConsumerState<ShareCardPage> createState() => _ShareCardPageState();
}
class _ShareCardPageState extends ConsumerState<ShareCardPage> {
final GlobalKey _previewKey = GlobalKey();
bool _sharing = false;
Future<void> _shareCard(String owner, {ui.Rect? origin}) async {
if (_sharing) return;
setState(() => _sharing = true);
try {
await sharePngFromBoundary(
_previewKey,
fileName: 'jinzhi_share_card.png',
text: '$owner 的贵金属估值卡,已通过金值生成。',
subject: '金值贵金属估值卡',
sharePositionOrigin: origin ?? shareOriginForContext(context),
);
} catch (e) {
debugPrint('share card failed: $e');
await Clipboard.setData(ClipboardData(text: '$owner 的贵金属估值卡,已通过金值生成。'));
if (!mounted) return;
toastInfo(msg: '图片分享失败,已复制分享文案');
} finally {
if (mounted) {
setState(() => _sharing = false);
}
}
}
@override
Widget build(BuildContext context) {
final auth = ref.watch(authProvider);
final currentUser = auth.valueOrNull is LoggedInAuthState
? (auth.valueOrNull as LoggedInAuthState).user
: null;
final nickname = currentUser?.nickname.trim();
final owner = nickname == null || nickname.isEmpty ? '金值用户' : nickname;
return _ActionScaffold(
title: '分享估值卡',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
RepaintBoundary(
key: _previewKey,
child: _SharePreviewCard(owner: owner),
),
const SizedBox(height: 14),
PrototypeCard(
padding: EdgeInsets.zero,
child: Column(
children: const [
_SettingLikeRow(
icon: Icons.visibility_off_outlined,
title: '金额隐私',
trailing: '已打码',
),
PrototypeDivider(margin: EdgeInsets.zero),
_SettingLikeRow(
icon: Icons.ios_share_outlined,
title: '分享渠道',
trailing: '系统分享',
),
],
),
),
const SizedBox(height: 14),
Builder(
builder: (buttonContext) => FilledButton.icon(
onPressed: _sharing
? null
: () {
final origin = shareOriginForContext(buttonContext);
_shareCard(owner, origin: origin);
},
icon: _sharing
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.ios_share_outlined),
label: Text(_sharing ? '分享中' : '系统分享图片'),
),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: '$owner 的贵金属估值卡,已通过金值生成。'),
);
if (!context.mounted) return;
toastInfo(msg: '分享文案已复制');
},
child: const Text('复制分享文案'),
),
],
),
);
}
}
class LocalBackupPage extends StatelessWidget {
const LocalBackupPage({super.key});
@override
Widget build(BuildContext context) {
return _ActionScaffold(
title: '本地导出备份',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PrototypeCard(
padding: EdgeInsets.zero,
child: Column(
children: const [
_SettingLikeRow(
icon: Icons.inventory_2_outlined,
title: '备份内容',
trailing: '资产 / 设置',
),
PrototypeDivider(margin: EdgeInsets.zero),
_SettingLikeRow(
icon: Icons.description_outlined,
title: '导出格式',
trailing: 'JSON',
),
PrototypeDivider(margin: EdgeInsets.zero),
_SettingLikeRow(
icon: Icons.lock_outline,
title: '存储位置',
trailing: '仅本机',
),
],
),
),
const SizedBox(height: 14),
FilledButton(
onPressed: () => toastInfo(msg: '已生成本地备份文件'),
child: const Text('生成备份'),
),
const SizedBox(height: 12),
const Text(
'备份文件包含资产、纯度系数、回收折扣与显示设置,不包含登录凭证。',
style: TextStyle(
fontSize: 11,
height: 1.6,
color: Color(0xFFB3AFA5),
),
),
],
),
);
}
}
class CloudBackupPage extends ConsumerWidget {
const CloudBackupPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final auth = ref.watch(authProvider);
final loggedIn = auth.valueOrNull is LoggedInAuthState;
return _ActionScaffold(
title: '云备份 · 多端同步',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PrototypeCard(
padding: EdgeInsets.zero,
child: Column(
children: [
_SettingLikeRow(
icon: Icons.cloud_done_outlined,
title: '同步状态',
trailing: loggedIn ? '可开启' : '登录解锁',
),
const PrototypeDivider(margin: EdgeInsets.zero),
const _SettingLikeRow(
icon: Icons.devices_outlined,
title: '多端同步',
trailing: '资产 / 设置',
),
const PrototypeDivider(margin: EdgeInsets.zero),
const _SettingLikeRow(
icon: Icons.lock_outline,
title: '数据策略',
trailing: '本地优先',
),
],
),
),
const SizedBox(height: 14),
FilledButton(
onPressed: () {
if (!loggedIn) {
context.push(AppRoutes.login);
} else {
toastInfo(msg: '云同步入口已打开');
}
},
child: Text(loggedIn ? '开启云备份' : '去登录'),
),
],
),
);
}
}
class WidgetSettingsPage extends StatelessWidget {
const WidgetSettingsPage({super.key});
@override
Widget build(BuildContext context) {
return _ActionScaffold(
title: '桌面小组件',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _WidgetPreview(),
const SizedBox(height: 14),
PrototypeCard(
padding: EdgeInsets.zero,
child: Column(
children: const [
_SettingLikeRow(
icon: Icons.dashboard_customize_outlined,
title: '组件尺寸',
trailing: '中号',
),
PrototypeDivider(margin: EdgeInsets.zero),
_SettingLikeRow(
icon: Icons.lock_outline,
title: '金额显示',
trailing: '跟随隐私设置',
),
],
),
),
const SizedBox(height: 14),
FilledButton(
onPressed: () => toastInfo(msg: '演示:打开系统小组件设置'),
child: const Text('添加到桌面'),
),
],
),
);
}
}
class PuritySettingsPage extends ConsumerWidget {
const PuritySettingsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(profileSettingsControllerProvider);
final groups = [
('黄金', ['足金9999', '足金999', '22K', '18K']),
('铂金', ['PT990', 'PT950']),
('白银', ['999银', '925银']),
];
return _ActionScaffold(
title: '纯度系数管理',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'材料价值 = 克重 × 纯度系数 × 现货价。系数可自定义,适配不同成色。',
style: TextStyle(
fontSize: 12,
height: 1.6,
color: Color(0xFF8A8780),
),
),
const SizedBox(height: 14),
for (final group in groups) ...[
_CenteredSectionLabel(group.$1),
PrototypeCard(
padding: EdgeInsets.zero,
child: Column(
children: [
for (final purity in group.$2) ...[
_MetricRow(
title: purity,
value: (settings.purityPresets[purity] ?? 1)
.toStringAsFixed(4),
onTap: () => toastInfo(msg: '演示:自定义「$purity」纯度系数'),
),
if (purity != group.$2.last)
const PrototypeDivider(margin: EdgeInsets.zero),
],
],
),
),
if (group != groups.last) const SizedBox(height: 12),
],
const SizedBox(height: 8),
const Text(
'点任一行可自定义(演示) · 系数仅影响材料价值估算',
style: TextStyle(
fontSize: 11,
height: 1.6,
color: Color(0xFFB3AFA5),
),
),
],
),
);
}
}
class RecycleDiscountPage extends ConsumerWidget {
const RecycleDiscountPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(profileSettingsControllerProvider);
final notifier = ref.read(profileSettingsControllerProvider.notifier);
final discount = settings.recycleDiscounts[MetalType.gold] ?? 0.97;
final preview = 483812.0 * discount;
return _ActionScaffold(
title: '回收折扣设置',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PrototypeCard(
padding: const EdgeInsets.all(14),
child: Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
decoration: BoxDecoration(
color: const Color(0xFFF8F7F3),
borderRadius: BorderRadius.circular(18),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Text(
'回收折扣系数',
style: TextStyle(
fontSize: 14,
color: Color(0xFF8A8780),
),
),
const Spacer(),
Text(
'${(discount * 100).round()}%',
style: const TextStyle(
fontSize: 34,
fontWeight: FontWeight.w700,
height: 1,
color: Color(0xFFB5862A),
),
),
],
),
const SizedBox(height: 14),
Slider(
min: 0.8,
max: 1.0,
divisions: 20,
value: discount.clamp(0.8, 1.0),
activeColor: const Color(0xFFB5862A),
inactiveColor: const Color(0xFFE2DED3),
onChanged: (value) {
for (final metal in MetalType.values) {
notifier.setRecycleDiscount(metal, value);
}
},
),
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'80%',
style: TextStyle(
fontSize: 13,
color: Color(0xFFB3AFA5),
),
),
Text(
'100%',
style: TextStyle(
fontSize: 13,
color: Color(0xFFB3AFA5),
),
),
],
),
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 18,
vertical: 16,
),
decoration: BoxDecoration(
color: const Color(0xFFF1EEE6),
borderRadius: BorderRadius.circular(18),
),
child: Row(
children: [
const Expanded(
child: Text(
'当前总回收参考',
style: TextStyle(
fontSize: 13,
color: Color(0xFF8A8780),
),
),
),
Text(
formatCny(preview),
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.w700,
height: 1,
),
),
],
),
),
],
),
),
),
const SizedBox(height: 14),
const Text(
'回收参考价 = 材料价值 × 折扣系数,恒标「参考」。同一系数也用于「兑换试算」的回收口径。',
style: TextStyle(
fontSize: 12,
height: 1.6,
color: Color(0xFF8A8780),
),
),
const SizedBox(height: 4),
const Text(
'实务中不同金属折扣不同(足金≈95%、铂金≈78%、白银更低)。',
style: TextStyle(
fontSize: 11,
height: 1.6,
color: Color(0xFFB3AFA5),
),
),
],
),
);
}
}
class PriceColorPage extends ConsumerWidget {
const PriceColorPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final cs = ShadTheme.of(context).colorScheme;
final current = ref.watch(profileSettingsControllerProvider).priceColorMode;
final notifier = ref.read(profileSettingsControllerProvider.notifier);
return _ActionScaffold(
title: '涨跌颜色',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PrototypeCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_ColorModeTile(
title: PriceColorMode.redUpGreenDown.label,
upColor: const Color(0xFFC0392B),
downColor: const Color(0xFF2E8B6F),
selected: current == PriceColorMode.redUpGreenDown,
onTap: () =>
notifier.setPriceColorMode(PriceColorMode.redUpGreenDown),
),
const SizedBox(height: 10),
_ColorModeTile(
title: PriceColorMode.greenUpRedDown.label,
upColor: const Color(0xFF2E8B6F),
downColor: const Color(0xFFC0392B),
selected: current == PriceColorMode.greenUpRedDown,
onTap: () =>
notifier.setPriceColorMode(PriceColorMode.greenUpRedDown),
),
],
),
),
const SizedBox(height: 12),
Text(
'当前预览: ${current.label}',
style: TextStyle(fontSize: 12, color: cs.mutedForeground),
),
],
),
);
}
}
class CurrencyPage extends StatelessWidget {
const CurrencyPage({super.key});
@override
Widget build(BuildContext context) {
return const _ActionScaffold(
title: '显示币种',
child: PrototypeCard(
padding: EdgeInsets.zero,
child: Column(
children: [
_CurrencyRow(label: '人民币', code: '¥ CNY', selected: true),
PrototypeDivider(margin: EdgeInsets.zero),
_CurrencyRow(label: '美元', code: r'$ USD', selected: false),
PrototypeDivider(margin: EdgeInsets.zero),
_CurrencyRow(label: '港币', code: r'HK$ HKD', selected: false),
],
),
),
);
}
}
class DisclaimerPage extends StatelessWidget {
const DisclaimerPage({super.key});
@override
Widget build(BuildContext context) {
const bg = Color(0xFFF8F6EF);
const textColor = Color(0xFF4B4A46);
const mutedColor = Color(0xFF6C6961);
return Scaffold(
backgroundColor: bg,
appBar: AppBar(
backgroundColor: bg,
surfaceTintColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
leading: IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.chevron_left, size: 26),
),
title: const Text(
'免责声明 · 数据来源',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: textColor,
),
),
),
body: SafeArea(
top: false,
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(28, 10, 28, 36),
child: DefaultTextStyle(
style: const TextStyle(
fontSize: 17,
height: 1.6,
color: textColor,
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_DisclaimerParagraph([
const TextSpan(text: 'App 内「材料价值」= 克重 × 纯度系数 × '),
const TextSpan(
text: '上海金现货参考价(元/克)。',
style: const TextStyle(fontWeight: FontWeight.w700),
),
]),
SizedBox(height: 22),
_DisclaimerParagraph([
const TextSpan(text: '它不是金店零售价(含工费/品牌溢价),也不'),
const TextSpan(
text: '是回收实收价。',
style: const TextStyle(fontWeight: FontWeight.w700),
),
]),
SizedBox(height: 22),
_DisclaimerParagraph([
const TextSpan(
text: '「兑换试算」中各渠道(金店 / 金条 / T+D /\n回收)报价均为参考价,恒标来源与时间,以\n实际门店 / 交易系统为准。',
),
]),
SizedBox(height: 22),
_DisclaimerParagraph([
const TextSpan(
text: '「回收参考价」= 材料价值 × 可调折扣系数,\n仅供参考;正式版将尽量对接真实回收报价。',
),
]),
SizedBox(height: 22),
_DisclaimerParagraph([
const TextSpan(
text: '行情数据:行情聚合 · 仅供参考,实际以交\n易系统/门店为准。',
style: const TextStyle(color: mutedColor),
),
]),
SizedBox(height: 22),
_DisclaimerParagraph([
const TextSpan(
text: '本品为资产记录与估值工具,所有信息均不\n构成投资建议。',
),
]),
],
),
),
),
),
);
}
}
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
return _ActionScaffold(
title: '意见反馈 · 关于金值',
child: FutureBuilder<PackageInfo>(
future: PackageInfo.fromPlatform(),
builder: (context, snapshot) {
final version = snapshot.data == null
? '加载中'
: 'Version ${snapshot.data!.version}+${snapshot.data!.buildNumber}';
return PrototypeCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'金值 · 贵金属资产管家',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
Text(version, style: const TextStyle(fontSize: 13)),
const SizedBox(height: 10),
const Text(
'以「我的贵金属资产实时估值」为中心,辅以行情判断与资讯。',
style: TextStyle(
fontSize: 13,
color: Color(0xFF8A8780),
height: 1.5,
),
),
const SizedBox(height: 16),
_GoldTextAction(
label: '提交意见反馈',
onTap: () => toastInfo(msg: '演示:打开反馈表单'),
),
const SizedBox(height: 10),
_GoldTextAction(
label: '检查更新',
onTap: () => toastInfo(msg: '当前已是最新版本'),
),
],
),
);
},
),
);
}
}
class _ActionScaffold extends StatelessWidget {
const _ActionScaffold({required this.title, required this.child});
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.background,
appBar: AppBar(
backgroundColor: cs.background,
surfaceTintColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
toolbarHeight: 46,
title: Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: cs.foreground,
),
),
leading: const CircleBackButton(),
),
body: ListView(
padding: Adaptive.screenPadding(context, top: 8, bottom: 32),
children: [child],
),
);
}
}
class _InviteCodeCard extends StatelessWidget {
const _InviteCodeCard();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF1A1A17), Color(0xFF3A2F12)],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Row(
children: [
_GoldDot(),
SizedBox(width: 7),
Text(
'邀请好友,一起记账各得奖励',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 14),
const Text(
'我的邀请码',
style: TextStyle(fontSize: 12, color: Color(0xFFC9C4B5)),
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.white.withValues(alpha: 0.30),
width: 0.5,
),
),
child: Row(
children: [
const Expanded(
child: Text(
'JINZHI-8K3F',
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.w700,
letterSpacing: 2,
),
),
),
GestureDetector(
onTap: () async {
await Clipboard.setData(
const ClipboardData(text: 'JINZHI-8K3F'),
);
if (!context.mounted) return;
toastInfo(msg: '已复制邀请码');
},
child: const Text(
'复制',
style: TextStyle(
color: Color(0xFFB5862A),
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
);
}
}
class _SharePreviewCard extends StatelessWidget {
const _SharePreviewCard({required this.owner});
final String owner;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF1A1A17), Color(0xFF3A2F12)],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Row(
children: [
_GoldDot(),
SizedBox(width: 7),
Text(
'金值 · 我的贵金属',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 20),
const Text(
'总估值',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 12, color: Color(0xFFC9C4B5)),
),
const SizedBox(height: 8),
const Text(
'••••••',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 42,
fontWeight: FontWeight.w700,
height: 1,
),
),
const SizedBox(height: 10),
Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFFC0392B).withValues(alpha: 0.22),
borderRadius: BorderRadius.circular(999),
),
child: const Text(
'•••• · +1.21% 今日',
style: TextStyle(
color: Color(0xFFF0A99E),
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 18),
Text(
'来自 $owner · 金额已自动打码',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 12, color: Color(0xFFC9C4B5)),
),
],
),
);
}
}
class _WidgetPreview extends StatelessWidget {
const _WidgetPreview();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
decoration: BoxDecoration(
color: const Color(0xFFE8E0D0),
borderRadius: BorderRadius.circular(24),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'6月15日 周日',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
color: Color(0xFF5C584C),
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF7F4EE),
borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0xFFDAD2C2), width: 0.5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Row(
children: [
_GoldDot(),
SizedBox(width: 7),
Text(
'金值 · 我的贵金属',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
),
),
Spacer(),
Text(
'16:11',
style: TextStyle(fontSize: 11, color: Color(0xFFB3AFA5)),
),
],
),
const SizedBox(height: 14),
const Text(
'¥ 483,812',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w700,
height: 1,
),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 9,
vertical: 4,
),
decoration: BoxDecoration(
color: const Color(0xFFFBEDEA),
borderRadius: BorderRadius.circular(999),
),
child: const Text(
'+¥5,821 · +1.21% 今日',
style: TextStyle(
fontSize: 12,
color: Color(0xFFC0392B),
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 14),
const Row(
children: [
Expanded(
child: _MiniMetal(label: '金', value: '64%'),
),
SizedBox(width: 8),
Expanded(
child: _MiniMetal(label: '铂', value: '22%'),
),
SizedBox(width: 8),
Expanded(
child: _MiniMetal(label: '银', value: '14%'),
),
],
),
],
),
),
const SizedBox(height: 16),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFF7F4EE),
borderRadius: BorderRadius.circular(22),
border: Border.all(
color: const Color(0xFFDAD2C2),
width: 0.5,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'黄金 元/克',
style: TextStyle(
fontSize: 12,
color: Color(0xFF8A8780),
),
),
const SizedBox(height: 4),
const Text(
'943.05',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w700,
height: 1,
),
),
const SizedBox(height: 4),
const Text(
'▲ +0.50%',
style: TextStyle(
fontSize: 12,
color: Color(0xFFC0392B),
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
SizedBox(
height: 32,
child: PrototypeLineChart(
values: const [
27,
31,
29,
35,
33,
37,
42,
40,
46,
44,
50,
53,
],
),
),
],
),
),
),
const SizedBox(width: 14),
const Expanded(
child: Column(
children: [
Row(
children: [
Expanded(
child: _MiniSquare(
color: Color(0xFFB5862A),
label: '金值',
),
),
SizedBox(width: 10),
Expanded(child: _MiniSquare(color: Color(0xFFCBA86A))),
],
),
SizedBox(height: 10),
Row(
children: [
Expanded(child: _MiniSquare(color: Color(0xFFB7BEC6))),
SizedBox(width: 10),
Expanded(child: _MiniSquare(color: Color(0xFFC7C2B5))),
],
),
],
),
),
],
),
],
),
);
}
}
class _MiniSquare extends StatelessWidget {
const _MiniSquare({required this.color, this.label});
final Color color;
final String? label;
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1,
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(16),
),
alignment: Alignment.center,
child: label == null
? null
: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.paid_outlined, color: Colors.white, size: 22),
SizedBox(height: 4),
Text(
'金值',
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
}
class _ColorModeTile extends StatelessWidget {
const _ColorModeTile({
required this.title,
required this.upColor,
required this.downColor,
required this.selected,
required this.onTap,
});
final String title;
final Color upColor;
final Color downColor;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: Container(
padding: const EdgeInsets.all(13),
decoration: BoxDecoration(
color: selected ? const Color(0xFFF6ECD6) : Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: selected ? const Color(0xFFE3D6B4) : const Color(0xFFECEAE3),
width: 0.5,
),
),
child: Row(
children: [
Expanded(
child: Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
_ColorSwatch(color: upColor, label: '涨'),
const SizedBox(width: 8),
_ColorSwatch(color: downColor, label: '跌'),
const SizedBox(width: 8),
Icon(
selected ? Icons.check_circle : Icons.radio_button_unchecked,
size: 18,
color: selected
? const Color(0xFFB5862A)
: const Color(0xFFB3AFA5),
),
],
),
),
);
}
}
class _CurrencyRow extends StatelessWidget {
const _CurrencyRow({
required this.label,
required this.code,
required this.selected,
});
final String label;
final String code;
final bool selected;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
children: [
Expanded(
child: Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: selected
? const Color(0xFF1F1D18)
: const Color(0xFFB3AFA5),
),
),
),
Text(
code,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: selected
? const Color(0xFFB5862A)
: const Color(0xFFB3AFA5),
),
),
const SizedBox(width: 8),
Icon(
selected ? Icons.check_circle : Icons.lock_outline,
size: 17,
color: selected ? const Color(0xFFB5862A) : const Color(0xFFB3AFA5),
),
],
),
);
}
}
class _SettingLikeRow extends StatelessWidget {
const _SettingLikeRow({
required this.icon,
required this.title,
required this.trailing,
});
final IconData icon;
final String title;
final String trailing;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
child: Row(
children: [
Icon(icon, size: 18, color: const Color(0xFFB5862A)),
const SizedBox(width: 10),
Expanded(
child: Text(
title,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
),
Text(
trailing,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF6E6A60),
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 4),
const Icon(Icons.chevron_right, size: 16, color: Color(0xFFB3AFA5)),
],
),
);
}
}
class _KeyValueLine extends StatelessWidget {
const _KeyValueLine({
required this.label,
required this.value,
this.valueColor,
});
final String label;
final String value;
final Color? valueColor;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
Expanded(
child: Text(
label,
style: const TextStyle(fontSize: 13, color: Color(0xFF8A8780)),
),
),
Text(
value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: valueColor ?? const Color(0xFF1F1D18),
),
),
],
),
);
}
}
class _GoldTextAction extends StatelessWidget {
const _GoldTextAction({required this.label, required this.onTap});
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Text(
'$label ',
style: const TextStyle(
color: Color(0xFFB5862A),
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
);
}
}
class _DisclaimerParagraph extends StatelessWidget {
const _DisclaimerParagraph(this.spans);
final List<InlineSpan> spans;
@override
Widget build(BuildContext context) {
return Text.rich(
TextSpan(
children: spans,
style: const TextStyle(
fontSize: 17,
height: 1.6,
color: Color(0xFF4B4A46),
),
),
);
}
}
class _CenteredSectionLabel extends StatelessWidget {
const _CenteredSectionLabel(this.label);
final String label;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Center(
child: Text(
label,
style: const TextStyle(fontSize: 12, color: Color(0xFF8A8780)),
),
),
);
}
}
class _MetricRow extends StatelessWidget {
const _MetricRow({
required this.title,
required this.value,
required this.onTap,
});
final String title;
final String value;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
child: Row(
children: [
Expanded(child: Text(title, style: const TextStyle(fontSize: 15))),
Text(
value,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Color(0xFF1F1D18),
),
),
const SizedBox(width: 6),
const Icon(Icons.chevron_right, size: 16, color: Color(0xFFB3AFA5)),
],
),
),
);
}
}
class _MiniMetal extends StatelessWidget {
const _MiniMetal({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE6E0D6), width: 0.5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontSize: 11, color: Color(0xFF8A8780)),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700),
),
],
),
);
}
}
class _ColorSwatch extends StatelessWidget {
const _ColorSwatch({required this.color, required this.label});
final Color color;
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(999),
),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: color,
),
),
);
}
}
class _GoldDot extends StatelessWidget {
const _GoldDot();
@override
Widget build(BuildContext context) {
return Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Color(0xFFB5862A),
shape: BoxShape.circle,
),
);
}
}