fix:金条详情和编辑,添加一致
This commit is contained in:
@@ -239,11 +239,19 @@ class HoldingDetailPage extends ConsumerWidget {
|
||||
),
|
||||
const PrototypeDivider(margin: EdgeInsets.zero),
|
||||
_InfoRow(label: '购买渠道', value: holding.channel ?? '未填'),
|
||||
const PrototypeDivider(margin: EdgeInsets.zero),
|
||||
_InfoRow(
|
||||
label: '备注',
|
||||
value: holding.note == null ||
|
||||
holding.note!.trim().isEmpty
|
||||
? '未填'
|
||||
: holding.note!.trim(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildImageSection(context, ref, holding),
|
||||
_buildImageSection(context, holding),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
@@ -370,7 +378,6 @@ class HoldingDetailPage extends ConsumerWidget {
|
||||
|
||||
Widget _buildImageSection(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
GoldAssetHolding holding,
|
||||
) {
|
||||
final images = holding.images;
|
||||
@@ -397,7 +404,6 @@ class HoldingDetailPage extends ConsumerWidget {
|
||||
for (var index = 0; index < images.length; index++)
|
||||
_buildDetailImageTile(
|
||||
context,
|
||||
ref,
|
||||
holding,
|
||||
images[index],
|
||||
index,
|
||||
@@ -411,38 +417,19 @@ class HoldingDetailPage extends ConsumerWidget {
|
||||
|
||||
Widget _buildDetailImageTile(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
GoldAssetHolding holding,
|
||||
AssetImage image,
|
||||
int index,
|
||||
) {
|
||||
return GestureDetector(
|
||||
onTap: () => _openImagePreview(context, holding.images, index),
|
||||
onLongPress: () => _confirmDeleteImage(context, ref, holding, image),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.file(
|
||||
File(image.localPath),
|
||||
width: 84,
|
||||
height: 84,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
Positioned(
|
||||
right: 4,
|
||||
top: 4,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xAA000000),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.remove, size: 14, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Image.file(
|
||||
File(image.localPath),
|
||||
width: 84,
|
||||
height: 84,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -463,39 +450,6 @@ class HoldingDetailPage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _confirmDeleteImage(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
GoldAssetHolding holding,
|
||||
AssetImage image,
|
||||
) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('删除照片'),
|
||||
content: const Text('确认删除这张凭证照片吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
final updated = holding.copyWith(
|
||||
images: List<AssetImage>.unmodifiable(
|
||||
holding.images.where((item) => item.localId != image.localId).toList(),
|
||||
),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
ref.read(assetPortfolioControllerProvider.notifier).updateHolding(updated);
|
||||
},
|
||||
child: const Text('删除'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionMenuTile extends StatelessWidget {
|
||||
@@ -609,233 +563,6 @@ class _ChangeChip extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _EditHoldingSheet extends StatefulWidget {
|
||||
const _EditHoldingSheet({required this.holding, required this.onSave});
|
||||
|
||||
final GoldAssetHolding holding;
|
||||
final ValueChanged<GoldAssetHolding> onSave;
|
||||
|
||||
@override
|
||||
State<_EditHoldingSheet> createState() => _EditHoldingSheetState();
|
||||
}
|
||||
|
||||
class _EditHoldingSheetState extends State<_EditHoldingSheet> {
|
||||
late final TextEditingController _weightCtrl;
|
||||
late final TextEditingController _costCtrl;
|
||||
late final TextEditingController _dateCtrl;
|
||||
late final TextEditingController _channelCtrl;
|
||||
late final TextEditingController _noteCtrl;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_weightCtrl = TextEditingController(
|
||||
text: widget.holding.weightGram.toStringAsFixed(
|
||||
widget.holding.weightGram % 1 == 0 ? 0 : 1,
|
||||
),
|
||||
);
|
||||
_costCtrl = TextEditingController(
|
||||
text: widget.holding.costAmount?.toStringAsFixed(0) ?? '',
|
||||
);
|
||||
_dateCtrl = TextEditingController(
|
||||
text: widget.holding.purchaseDate == null
|
||||
? ''
|
||||
: '${widget.holding.purchaseDate!.year}-${widget.holding.purchaseDate!.month.toString().padLeft(2, '0')}-${widget.holding.purchaseDate!.day.toString().padLeft(2, '0')}',
|
||||
);
|
||||
_channelCtrl = TextEditingController(text: widget.holding.channel ?? '');
|
||||
_noteCtrl = TextEditingController(text: widget.holding.note ?? '');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_weightCtrl.dispose();
|
||||
_costCtrl.dispose();
|
||||
_dateCtrl.dispose();
|
||||
_channelCtrl.dispose();
|
||||
_noteCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = ShadTheme.of(context).colorScheme;
|
||||
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.stretch,
|
||||
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),
|
||||
_FieldRow(
|
||||
label: '金属 / 纯度',
|
||||
child: Text(
|
||||
'${widget.holding.metal.label} · ${widget.holding.purityLabel}',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_FieldRow(
|
||||
label: '克重(克)',
|
||||
child: TextField(
|
||||
controller: _weightCtrl,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_FieldRow(
|
||||
label: '买入成本(含工费)',
|
||||
child: TextField(
|
||||
controller: _costCtrl,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
hintText: '选填',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_FieldRow(
|
||||
label: '购买日期',
|
||||
child: TextField(
|
||||
controller: _dateCtrl,
|
||||
textAlign: TextAlign.right,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
hintText: 'YYYY-MM-DD',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_FieldRow(
|
||||
label: '购买渠道',
|
||||
child: TextField(
|
||||
controller: _channelCtrl,
|
||||
textAlign: TextAlign.right,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
hintText: '选填',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_FieldRow(
|
||||
label: '备注',
|
||||
child: TextField(
|
||||
controller: _noteCtrl,
|
||||
textAlign: TextAlign.right,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
hintText: '点击填写',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
final grams = double.tryParse(_weightCtrl.text.trim());
|
||||
final cost = double.tryParse(_costCtrl.text.trim());
|
||||
final parsedDate = DateTime.tryParse(
|
||||
_dateCtrl.text.trim(),
|
||||
);
|
||||
widget.onSave(
|
||||
GoldAssetHolding(
|
||||
id: widget.holding.id,
|
||||
name: widget.holding.name,
|
||||
metal: widget.holding.metal,
|
||||
category: widget.holding.category,
|
||||
purity: widget.holding.purity,
|
||||
purityLabel: widget.holding.purityLabel,
|
||||
weightGram: grams ?? widget.holding.weightGram,
|
||||
costAmount: cost,
|
||||
purchaseDate: parsedDate,
|
||||
channel: _channelCtrl.text.trim().isEmpty
|
||||
? null
|
||||
: _channelCtrl.text.trim(),
|
||||
note: _noteCtrl.text.trim().isEmpty
|
||||
? null
|
||||
: _noteCtrl.text.trim(),
|
||||
createdAt: widget.holding.createdAt,
|
||||
updatedAt: DateTime.now(),
|
||||
status: widget.holding.status,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('保存修改'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'修改后会即时重算资产总值',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 11, color: cs.mutedForeground),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FieldRow extends StatelessWidget {
|
||||
const _FieldRow({required this.label, required this.child});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user