1204 lines
35 KiB
Dart
1204 lines
35 KiB
Dart
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 '../../../common/domain/metal_type.dart';
|
||
import '../../../common/domain/money.dart';
|
||
import '../../../common/router/app_router.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: () => _toast(context, '演示:拉起微信 / 系统分享面板'),
|
||
child: const Text('分享给好友'),
|
||
),
|
||
const SizedBox(height: 14),
|
||
const Text(
|
||
'好友通过你的邀请码注册并完成首次记账,双方各得奖励。奖励权益将随产品逐步开放,具体以活动规则为准。',
|
||
style: TextStyle(
|
||
fontSize: 11,
|
||
height: 1.6,
|
||
color: Color(0xFFB3AFA5),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class ShareCardPage extends ConsumerWidget {
|
||
const ShareCardPage({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
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: [
|
||
_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),
|
||
FilledButton(
|
||
onPressed: () async {
|
||
await Clipboard.setData(
|
||
ClipboardData(text: '$owner 的贵金属估值卡,已通过金值生成。'),
|
||
);
|
||
if (!context.mounted) return;
|
||
_toast(context, '分享文案已复制');
|
||
},
|
||
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: () => _toast(context, '已生成本地备份文件'),
|
||
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 {
|
||
_toast(context, '云同步入口已打开');
|
||
}
|
||
},
|
||
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: () => _toast(context, '演示:打开系统小组件设置'),
|
||
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']),
|
||
('铂金', ['PT950']),
|
||
('白银', ['999银']),
|
||
];
|
||
|
||
return _ActionScaffold(
|
||
title: '纯度系数管理',
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
for (final group in groups) ...[
|
||
_SectionLabel(group.$1),
|
||
PrototypeCard(
|
||
padding: EdgeInsets.zero,
|
||
child: Column(
|
||
children: [
|
||
for (final purity in group.$2) ...[
|
||
_SettingLikeRow(
|
||
icon: Icons.tune_outlined,
|
||
title: purity,
|
||
trailing: (settings.purityPresets[purity] ?? 1)
|
||
.toStringAsFixed(4),
|
||
onTap: () => _toast(context, '演示:自定义「$purity」纯度系数'),
|
||
),
|
||
if (purity != group.$2.last)
|
||
const PrototypeDivider(margin: EdgeInsets.zero),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
if (group != groups.last) const SizedBox(height: 12),
|
||
],
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
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 preview =
|
||
483812.0 * (settings.recycleDiscounts[MetalType.gold] ?? 0.97);
|
||
|
||
return _ActionScaffold(
|
||
title: '回收折扣设置',
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
PrototypeCard(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'回收参考预览',
|
||
style: TextStyle(fontSize: 13, color: Color(0xFF8A8780)),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
formatCny(preview),
|
||
style: const TextStyle(
|
||
fontSize: 28,
|
||
fontWeight: FontWeight.w700,
|
||
height: 1,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
const Text(
|
||
'按当前资产总估值和黄金折扣演示,实际以持仓金属分层计算。',
|
||
style: TextStyle(
|
||
fontSize: 11,
|
||
height: 1.5,
|
||
color: Color(0xFFB3AFA5),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 14),
|
||
PrototypeCard(
|
||
padding: EdgeInsets.zero,
|
||
child: Column(
|
||
children: [
|
||
for (final entry in settings.recycleDiscounts.entries) ...[
|
||
_DiscountSliderRow(
|
||
metal: entry.key,
|
||
value: entry.value,
|
||
onChanged: (value) =>
|
||
notifier.setRecycleDiscount(entry.key, value),
|
||
),
|
||
if (entry.key != settings.recycleDiscounts.keys.last)
|
||
const PrototypeDivider(margin: EdgeInsets.zero),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
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) {
|
||
return const _ActionScaffold(
|
||
title: '免责声明 · 数据来源',
|
||
child: PrototypeCard(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_InfoParagraph('App 内「材料价值」= 克重 × 纯度系数 × 上海金现货参考价(元/克)。'),
|
||
_InfoParagraph('它不是金店零售价(含工费 / 品牌溢价),也不是回收实收价。'),
|
||
_InfoParagraph(
|
||
'「兑换试算」中各渠道(金店 / 金条 / T+D / 回收)报价均为参考价,恒标来源与时间,以实际门店 / 交易系统为准。',
|
||
),
|
||
_InfoParagraph('「回收参考价」= 材料价值 × 可调折扣系数,仅供参考;正式版将尽量对接真实回收报价。'),
|
||
_InfoParagraph('本品为资产记录与估值工具,所有信息均不构成投资建议。'),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
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: () => _toast(context, '演示:打开反馈表单'),
|
||
),
|
||
const SizedBox(height: 10),
|
||
_GoldTextAction(
|
||
label: '检查更新',
|
||
onTap: () => _toast(context, '当前已是最新版本'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
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;
|
||
_toast(context, '已复制邀请码');
|
||
},
|
||
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.all(18),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFF1A1A17),
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF7F4EE),
|
||
borderRadius: BorderRadius.circular(18),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
_GoldDot(),
|
||
SizedBox(width: 7),
|
||
Text(
|
||
'金值',
|
||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700),
|
||
),
|
||
],
|
||
),
|
||
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%'),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _DiscountSliderRow extends StatelessWidget {
|
||
const _DiscountSliderRow({
|
||
required this.metal,
|
||
required this.value,
|
||
required this.onChanged,
|
||
});
|
||
|
||
final MetalType metal;
|
||
final double value;
|
||
final ValueChanged<double> onChanged;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
|
||
child: Column(
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Container(
|
||
width: 8,
|
||
height: 8,
|
||
decoration: BoxDecoration(
|
||
color: metal.color,
|
||
shape: BoxShape.circle,
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
metal.label,
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
),
|
||
Text(
|
||
'${(value * 100).round()}%',
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w700,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
Slider(
|
||
min: 0.7,
|
||
max: 1.0,
|
||
divisions: 30,
|
||
value: value.clamp(0.7, 1.0),
|
||
activeColor: const Color(0xFFB5862A),
|
||
inactiveColor: const Color(0xFFECEAE3),
|
||
onChanged: onChanged,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
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,
|
||
this.onTap,
|
||
});
|
||
|
||
final IconData icon;
|
||
final String title;
|
||
final String trailing;
|
||
final VoidCallback? onTap;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final content = 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,
|
||
),
|
||
),
|
||
if (onTap != null) ...[
|
||
const SizedBox(width: 4),
|
||
const Icon(Icons.chevron_right, size: 16, color: Color(0xFFB3AFA5)),
|
||
],
|
||
],
|
||
),
|
||
);
|
||
if (onTap == null) return content;
|
||
return InkWell(onTap: onTap, child: content);
|
||
}
|
||
}
|
||
|
||
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 _InfoParagraph extends StatelessWidget {
|
||
const _InfoParagraph(this.text);
|
||
|
||
final String text;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 10),
|
||
child: Text(
|
||
text,
|
||
style: const TextStyle(
|
||
fontSize: 13,
|
||
height: 1.65,
|
||
color: Color(0xFF6E6A60),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _SectionLabel extends StatelessWidget {
|
||
const _SectionLabel(this.label);
|
||
|
||
final String label;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: const EdgeInsets.fromLTRB(2, 0, 2, 7),
|
||
child: Text(
|
||
label,
|
||
style: const TextStyle(fontSize: 12, color: Color(0xFF8A8780)),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
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,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
void _toast(BuildContext context, String message) {
|
||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
|
||
}
|