1544 lines
47 KiB
Dart
1544 lines
47 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 '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,
|
||
),
|
||
);
|
||
}
|
||
}
|