Files
jinzhi/lib/features/assets/presentation/assets_page.dart
T
2026-06-18 16:53:08 +08:00

952 lines
35 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../../../common/domain/metal_type.dart';
import '../../../common/domain/money.dart';
import '../../../common/router/app_router.dart';
import '../../../common/widgets/prototype_ui.dart';
import '../application/asset_portfolio_controller.dart';
import '../data/asset_models.dart';
import '../../market/application/market_controller.dart';
import '../../profile/application/profile_settings_controller.dart';
class AssetsPage extends ConsumerWidget {
const AssetsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final summary = ref.watch(assetPortfolioControllerProvider);
final settings = ref.watch(profileSettingsControllerProvider);
final cs = ShadTheme.of(context).colorScheme;
final hidden = settings.amountHidden;
return SafeArea(
top: false,
child: ListView(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 86),
children: [
PrototypeFrame(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Row(
children: [
_Dot(),
SizedBox(width: 8),
Text(
'金值',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
),
),
],
),
const Spacer(),
IconButton(
onPressed: () => _showAddAssetSheet(context),
icon: const Icon(Icons.add, size: 22),
color: const Color(0xFF9A968D),
padding: EdgeInsets.zero,
constraints: const BoxConstraints.tightFor(
width: 30,
height: 30,
),
),
IconButton(
onPressed: () => ref
.read(profileSettingsControllerProvider.notifier)
.toggleAmountHidden(),
icon: Icon(
hidden ? Icons.visibility_off_outlined : Icons.visibility_outlined,
size: 20,
),
color: const Color(0xFF9A968D),
padding: EdgeInsets.zero,
constraints: const BoxConstraints.tightFor(
width: 30,
height: 30,
),
),
],
),
const SizedBox(height: 26),
Text(
'我的贵金属总估值',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 13, color: cs.mutedForeground),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(top: 11),
child: Text(
'¥',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 3),
Text(
hidden ? '••••••' : formatCny(summary.totalValue).replaceFirst('¥', ''),
style: const TextStyle(
fontSize: 64,
fontWeight: FontWeight.w600,
height: 1,
letterSpacing: -2,
),
),
],
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_ChangeChip(
amount: summary.todayChangeAmount,
percent: summary.todayChangePercent,
),
const SizedBox(width: 10),
Text(
'今日',
style: TextStyle(fontSize: 12, color: cs.mutedForeground),
),
],
),
const SizedBox(height: 14),
SizedBox(
height: 32,
child: ListView.separated(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
itemCount: summary.breakdowns.length,
separatorBuilder: (context, index) => const SizedBox(width: 6),
itemBuilder: (context, index) {
final item = summary.breakdowns[index];
return PrototypePill(
label: '${item.metal.shortLabel} ${hidden ? '•••' : formatCny(item.value)}',
leading: Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: item.metal.color,
shape: BoxShape.circle,
),
),
);
},
),
),
const SizedBox(height: 14),
Text(
'更新于 16:11 · 行情聚合',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11, color: cs.mutedForeground.withValues(alpha: 0.82)),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () => context.push(
'${AppRoutes.marketKline}?metal=gold&period=24h',
),
child: SizedBox(
height: 46,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: PrototypeLineChart(
values: const [27, 31, 29, 35, 33, 37, 42, 40, 46, 44, 50, 53],
),
),
),
),
const SizedBox(height: 14),
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.6,
children: [
_StatCard(title: '回收参考', value: hidden ? '¥••••' : formatCny(summary.recycleReferenceValue)),
_StatCard(
title: '总盈亏(已填成本)',
value: hidden
? '••••'
: _signedCny(summary.totalValue - summary.totalCost),
valueColor: summary.totalValue - summary.totalCost >= 0
? const Color(0xFFC0392B)
: const Color(0xFF2E8B6F),
),
_StatCard(title: '总成本', value: hidden ? '¥••••' : formatCny(summary.totalCost)),
_StatCard(
title: '总克重',
value: '${summary.totalWeightGram.toStringAsFixed(1)} g',
),
],
),
const SizedBox(height: 12),
PrototypeCard(
padding: EdgeInsets.zero,
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 15, 16, 8),
child: Row(
children: [
const Text(
'我的持仓',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
Text(
'${summary.holdings.length} 件 · ${summary.totalWeightGram.toStringAsFixed(1)}g',
style: TextStyle(
fontSize: 12,
color: cs.mutedForeground,
),
),
],
),
),
const PrototypeDivider(margin: EdgeInsets.zero),
for (final item in summary.holdings) ...[
_HoldingRow(
valuation: item,
hidden: hidden,
onTap: () => context.push(
'${AppRoutes.assetDetail}?id=${item.holding.id}',
),
),
if (item != summary.holdings.last)
const PrototypeDivider(margin: EdgeInsets.zero),
],
const PrototypeDivider(margin: EdgeInsets.zero),
Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 16),
child: SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: () => _showAddAssetSheet(context),
icon: const Icon(Icons.add, size: 18),
label: const Text('添加资产'),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF9A7A2A),
side: const BorderSide(color: Color(0xFFE3D6B4)),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
),
],
),
),
],
),
),
],
),
);
}
void _showAddAssetSheet(BuildContext context) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(22)),
),
builder: (ctx) => const _AddAssetSheet(),
);
}
String _signedCny(double value) {
final sign = value >= 0 ? '+' : '-';
return '$sign${formatCny(value.abs())}';
}
}
class _HoldingRow extends StatelessWidget {
const _HoldingRow({
required this.valuation,
required this.hidden,
required this.onTap,
});
final HoldingValuation valuation;
final bool hidden;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final holding = valuation.holding;
final metal = holding.metal;
final change = valuation.todayChange;
final changeUp = change >= 0;
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
child: Row(
children: [
Container(
width: 34,
height: 34,
decoration: BoxDecoration(
color: metal.color.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(10),
),
child: Icon(_iconFor(holding.category), size: 18, color: metal.color),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
holding.name,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
'${holding.purityLabel} · ${holding.weightGram.toStringAsFixed(1)}g'
'${holding.note == null ? '' : ' · ${holding.note}'}',
style: const TextStyle(fontSize: 12, color: Color(0xFF8A8780)),
),
],
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
hidden ? '¥••••' : formatCny(valuation.materialValue),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
'${changeUp ? '↑' : '↓'}${hidden ? '¥••••' : formatCny(change.abs())}',
style: TextStyle(
fontSize: 12,
color: changeUp ? const Color(0xFFC0392B) : const Color(0xFF2E8B6F),
),
),
],
),
],
),
),
);
}
IconData _iconFor(AssetCategory category) {
return switch (category) {
AssetCategory.bar || AssetCategory.bean => Icons.auto_awesome,
AssetCategory.ring => Icons.radio_button_unchecked,
AssetCategory.necklace => Icons.circle_outlined,
AssetCategory.bracelet => Icons.watch_outlined,
AssetCategory.earring => Icons.circle,
AssetCategory.silverware => Icons.circle,
AssetCategory.platinumPiece => Icons.circle,
};
}
}
class _StatCard extends StatelessWidget {
const _StatCard({
required this.title,
required this.value,
this.valueColor,
});
final String title;
final String value;
final Color? valueColor;
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return PrototypeCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: TextStyle(fontSize: 12, color: cs.mutedForeground)),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: valueColor ?? cs.foreground,
),
),
],
),
);
}
}
class _ChangeChip extends StatelessWidget {
const _ChangeChip({required this.amount, required this.percent});
final double amount;
final double percent;
@override
Widget build(BuildContext context) {
final positive = amount >= 0;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 5),
decoration: BoxDecoration(
color: positive ? const Color(0xFFFBEDEA) : const Color(0xFFE7F2EE),
borderRadius: BorderRadius.circular(999),
),
child: Text(
'${positive ? '↑' : '↓'}${formatCny(amount.abs())} · ${positive ? '+' : '-'}${percent.abs().toStringAsFixed(2)}%',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: positive ? const Color(0xFFC0392B) : const Color(0xFF2E8B6F),
),
),
);
}
}
class _Dot extends StatelessWidget {
const _Dot();
@override
Widget build(BuildContext context) {
return Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Color(0xFFB5862A),
shape: BoxShape.circle,
),
);
}
}
class _AddAssetSheet extends ConsumerStatefulWidget {
const _AddAssetSheet();
@override
ConsumerState<_AddAssetSheet> createState() => _AddAssetSheetState();
}
class _AddAssetSheetState extends ConsumerState<_AddAssetSheet> {
static const _cats = [
AssetCategory.bar,
AssetCategory.necklace,
AssetCategory.ring,
AssetCategory.bracelet,
AssetCategory.bean,
AssetCategory.earring,
AssetCategory.silverware,
AssetCategory.platinumPiece,
];
static const _metalPurities = {
MetalType.gold: ['足金9999', '足金999', '22K', '18K'],
MetalType.platinum: ['PT990', 'PT950'],
MetalType.silver: ['999银', '925银'],
};
AssetCategory _cat = AssetCategory.bar;
MetalType _metal = MetalType.gold;
int _purityIndex = 0;
double _gram = 50;
bool _expanded = false;
final _costCtrl = TextEditingController();
final _noteCtrl = TextEditingController();
final _dateCtrl = TextEditingController(text: '2025-08-12');
final _channelCtrl = TextEditingController(text: '银行');
@override
void dispose() {
_costCtrl.dispose();
_noteCtrl.dispose();
_dateCtrl.dispose();
_channelCtrl.dispose();
super.dispose();
}
List<String> get _purityOptions => _metalPurities[_metal]!;
String get _purity => _purityOptions[_purityIndex.clamp(0, _purityOptions.length - 1)];
void _cyclePurity() {
setState(() {
_purityIndex = (_purityIndex + 1) % _purityOptions.length;
});
}
double _factorFor(String purity) {
return switch (purity) {
'足金9999' => 0.9999,
'足金999' => 0.999,
'22K' => 0.916,
'18K' => 0.75,
'PT990' => 0.99,
'PT950' => 0.95,
'999银' => 0.999,
'925银' => 0.925,
_ => 1,
};
}
String _nameForSelection() {
return switch (_cat) {
AssetCategory.bar => '投资金条',
AssetCategory.necklace => '${_metal.label}项链',
AssetCategory.ring => '${_metal.label}戒指',
AssetCategory.bracelet => '${_metal.label}手镯',
AssetCategory.bean => '金豆',
AssetCategory.earring => '${_metal.label}耳环',
AssetCategory.silverware => '银饰',
AssetCategory.platinumPiece => 'PT饰品',
};
}
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
final market = ref.watch(marketControllerProvider);
final basePrice = market.quote.spotPrice;
final value = _gram * _factorFor(_purity) * basePrice;
return SafeArea(
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Container(
width: 38,
height: 4,
margin: const EdgeInsets.only(bottom: 14),
decoration: BoxDecoration(
color: const Color(0xFFE3DFD4),
borderRadius: BorderRadius.circular(999),
),
),
),
Row(
children: [
const Spacer(),
const Text(
'添加资产',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const Spacer(),
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: const Icon(Icons.close, size: 20, color: Color(0xFF8A8780)),
),
],
),
const SizedBox(height: 16),
const Text('选择品类', style: TextStyle(fontSize: 13, color: Color(0xFF8A8780))),
const SizedBox(height: 10),
Wrap(
spacing: 9,
runSpacing: 9,
children: [
for (final cat in _cats)
_Chip(
label: cat.label,
selected: cat == _cat,
onTap: () => setState(() {
_cat = cat;
if (cat == AssetCategory.silverware) {
_metal = MetalType.silver;
} else if (cat == AssetCategory.platinumPiece) {
_metal = MetalType.platinum;
} else {
_metal = MetalType.gold;
}
_purityIndex = 0;
}),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.fromLTRB(16, 13, 16, 13),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFFE3D6B4), width: 0.5),
borderRadius: BorderRadius.circular(13),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('金属 / 纯度', style: TextStyle(fontSize: 13, color: Color(0xFF8A8780))),
GestureDetector(
onTap: _cyclePurity,
child: Row(
children: [
Text(
'${_metal.label} · $_purity',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
const SizedBox(width: 7),
const Icon(Icons.unfold_more, size: 16, color: Color(0xFFB3AFA5)),
],
),
),
],
),
),
const SizedBox(height: 16),
const Text('克重(克)', style: TextStyle(fontSize: 13, color: Color(0xFF8A8780))),
const SizedBox(height: 9),
Row(
children: [
_StepButton(
icon: Icons.remove,
onTap: () => setState(() => _gram = (_gram - 1).clamp(1, 200)),
),
const SizedBox(width: 12),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFFE3D6B4), width: 0.5),
borderRadius: BorderRadius.circular(13),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
_gram.toStringAsFixed(_gram % 1 == 0 ? 0 : 1),
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w600, height: 1),
),
const SizedBox(width: 6),
const Padding(
padding: EdgeInsets.only(bottom: 2),
child: Text('克', style: TextStyle(fontSize: 15, color: Color(0xFF8A8780))),
),
],
),
),
),
const SizedBox(width: 12),
_StepButton(
icon: Icons.add,
onTap: () => setState(() => _gram = (_gram + 1).clamp(1, 200)),
),
],
),
Slider(
min: 1,
max: 200,
value: _gram.clamp(1, 200),
onChanged: (value) => setState(() => _gram = value),
activeColor: const Color(0xFFB5862A),
inactiveColor: const Color(0xFFECEAE3),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.fromLTRB(16, 15, 16, 16),
decoration: BoxDecoration(
color: const Color(0xFFF6ECD6),
borderRadius: BorderRadius.circular(14),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('实时材料价值', style: TextStyle(fontSize: 13, color: Color(0xFF7A5B12))),
Text(
formatCny(value),
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 7),
Text(
'按${_metal.label}现货 ${basePrice.toStringAsFixed(2)}/克 · 非金店零售价、非回收实收价',
style: const TextStyle(fontSize: 11, color: Color(0xFF9A7A2A)),
),
],
),
),
const SizedBox(height: 14),
GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
child: Container(
padding: const EdgeInsets.fromLTRB(16, 13, 16, 13),
decoration: BoxDecoration(
color: cs.card,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.border, width: 0.5),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'选填:买入成本 · 日期 · 渠道 · 备注 · 照片',
style: TextStyle(fontSize: 13, color: cs.mutedForeground),
),
Icon(
_expanded ? Icons.expand_less : Icons.expand_more,
color: cs.mutedForeground,
size: 18,
),
],
),
),
),
if (_expanded) ...[
const SizedBox(height: 12),
_FieldRow(
label: '买入成本(含工费)',
child: TextField(
controller: _costCtrl,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
hintText: '选填',
),
textAlign: TextAlign.right,
),
),
const SizedBox(height: 10),
_FieldRow(
label: '购买日期',
child: TextField(
controller: _dateCtrl,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
),
textAlign: TextAlign.right,
),
),
const SizedBox(height: 10),
_FieldRow(
label: '购买渠道',
child: Wrap(
spacing: 7,
runSpacing: 7,
alignment: WrapAlignment.end,
children: [
for (final channel in ['银行', '金店', '电商', '回收市场', '其他'])
_Chip(
label: channel,
selected: _channelCtrl.text == channel,
onTap: () => setState(() => _channelCtrl.text = channel),
),
],
),
),
const SizedBox(height: 10),
_FieldRow(
label: '备注',
child: TextField(
controller: _noteCtrl,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
hintText: '点击填写',
),
textAlign: TextAlign.right,
),
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.fromLTRB(14, 14, 14, 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFECEAE3), width: 0.5),
),
child: Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(11),
border: Border.all(color: const Color(0xFFD3CDBE), width: 0.5),
),
child: const Icon(Icons.camera_alt_outlined, color: Color(0xFFB3AFA5)),
),
const SizedBox(width: 10),
const Expanded(
child: Text(
'添加发票 / 实物照片(选填,仅存本机)',
style: TextStyle(fontSize: 12, color: Color(0xFFB3AFA5)),
),
),
],
),
),
],
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFFE3D6B4)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: const Text('取消'),
),
),
const SizedBox(width: 10),
Expanded(
child: FilledButton(
onPressed: () {
final holding = GoldAssetHolding(
id: 'h${DateTime.now().microsecondsSinceEpoch}',
name: _nameForSelection(),
metal: _metal,
category: _cat,
purity: _factorFor(_purity),
purityLabel: _purity,
weightGram: _gram,
costAmount: double.tryParse(_costCtrl.text.trim()),
purchaseDate: DateTime.tryParse(_dateCtrl.text.trim()),
channel: _channelCtrl.text.trim().isEmpty ? null : _channelCtrl.text.trim(),
note: _noteCtrl.text.trim().isEmpty ? null : _noteCtrl.text.trim(),
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
ref.read(assetPortfolioControllerProvider.notifier).addHolding(holding);
Navigator.of(context).pop();
},
child: const Text('保存'),
),
),
],
),
],
),
),
),
),
);
}
}
class _Chip extends StatelessWidget {
const _Chip({
required this.label,
required this.selected,
required this.onTap,
});
final String label;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 6),
decoration: BoxDecoration(
color: selected ? const Color(0xFFF6ECD6) : Colors.white,
borderRadius: BorderRadius.circular(999),
border: Border.all(
color: selected ? const Color(0xFFE3D6B4) : const Color(0xFFECEAE3),
width: 0.5,
),
),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
color: selected ? const Color(0xFF7A5B12) : const Color(0xFF5F5E5A),
),
),
),
);
}
}
class _StepButton extends StatelessWidget {
const _StepButton({required this.icon, required this.onTap});
final IconData icon;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 34,
height: 34,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFFE3D6B4), width: 0.5),
),
child: Icon(icon, size: 18, color: const Color(0xFFB5862A)),
),
);
}
}
class _FieldRow extends StatelessWidget {
const _FieldRow({required this.label, required this.child});
final String label;
final Widget child;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFECEAE3), width: 0.5),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF8A8780))),
const SizedBox(width: 10),
Expanded(child: child),
],
),
);
}
}