improve parsing
This commit is contained in:
+72
-18
@@ -13,6 +13,7 @@ import 'utils/money.dart';
|
||||
import 'utils/supermarkets.dart';
|
||||
import 'widgets/discounted_price.dart';
|
||||
import 'widgets/item_edit_sheet.dart';
|
||||
import 'widgets/receipt_highlight_overlay.dart';
|
||||
import 'widgets/store_tag.dart';
|
||||
import 'widgets/tag_editor.dart';
|
||||
|
||||
@@ -26,6 +27,8 @@ class ReviewScreen extends StatefulWidget {
|
||||
this.supermarketNames = kSupermarkets,
|
||||
this.needsReview = false,
|
||||
this.detectedStore,
|
||||
this.ocrImageWidth,
|
||||
this.ocrImageHeight,
|
||||
});
|
||||
|
||||
final List<LineItem> items;
|
||||
@@ -35,6 +38,8 @@ class ReviewScreen extends StatefulWidget {
|
||||
final List<String> supermarketNames;
|
||||
final bool needsReview;
|
||||
final String? detectedStore;
|
||||
final double? ocrImageWidth;
|
||||
final double? ocrImageHeight;
|
||||
|
||||
@override
|
||||
State<ReviewScreen> createState() => _ReviewScreenState();
|
||||
@@ -49,6 +54,7 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
||||
final _tripName = TextEditingController();
|
||||
List<String> _tags = [];
|
||||
List<String> _tagSuggestions = [];
|
||||
String? _highlightedItemId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -88,6 +94,11 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
||||
return _supermarket;
|
||||
}
|
||||
|
||||
bool get _hasReceipt {
|
||||
final path = widget.imagePath;
|
||||
return path != null && path.isNotEmpty && File(path).existsSync();
|
||||
}
|
||||
|
||||
Future<void> _edit({LineItem? existing}) async {
|
||||
final result = await showItemEditSheet(context, item: existing);
|
||||
if (result == null || !mounted) return;
|
||||
@@ -98,9 +109,20 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
||||
} else {
|
||||
final index = _items.indexWhere((item) => item.id == existing.id);
|
||||
if (index >= 0) {
|
||||
_items[index] = result;
|
||||
_items[index] = result.copyWith(
|
||||
sourceBounds: existing.sourceBounds,
|
||||
needsManualReview: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
_highlightedItemId = null;
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleHighlight(LineItem item) {
|
||||
if (item.sourceBounds == null || !_hasReceipt) return;
|
||||
setState(() {
|
||||
_highlightedItemId = _highlightedItemId == item.id ? null : item.id;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,9 +203,12 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
||||
names.add(_supermarket!);
|
||||
}
|
||||
final receiptPath = widget.imagePath;
|
||||
final hasReceipt = receiptPath != null &&
|
||||
receiptPath.isNotEmpty &&
|
||||
File(receiptPath).existsSync();
|
||||
final highlighted = _highlightedItemId == null
|
||||
? null
|
||||
: _items.cast<LineItem?>().firstWhere(
|
||||
(item) => item?.id == _highlightedItemId,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -207,7 +232,7 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
'Parsed totals do not match the receipt. Check the items before saving.',
|
||||
'Item count or totals do not match the receipt, or some lines need manual review. Check highlighted items before saving.',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onErrorContainer,
|
||||
),
|
||||
@@ -325,6 +350,29 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
||||
});
|
||||
},
|
||||
child: ListTile(
|
||||
selected: _highlightedItemId == item.id,
|
||||
selectedTileColor: theme.colorScheme.primaryContainer
|
||||
.withValues(alpha: 0.35),
|
||||
leading: item.sourceBounds != null && _hasReceipt
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_highlightedItemId == item.id
|
||||
? Icons.visibility
|
||||
: Icons.visibility_outlined,
|
||||
size: 20,
|
||||
),
|
||||
tooltip: 'Show on receipt',
|
||||
onPressed: _saving
|
||||
? null
|
||||
: () => _toggleHighlight(item),
|
||||
)
|
||||
: item.needsManualReview
|
||||
? Icon(
|
||||
Icons.flag_outlined,
|
||||
color: theme.colorScheme.error,
|
||||
size: 20,
|
||||
)
|
||||
: null,
|
||||
title: Text(item.name),
|
||||
subtitle: _itemSubtitle(item),
|
||||
trailing: DiscountedPrice(item: item),
|
||||
@@ -337,24 +385,30 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (hasReceipt)
|
||||
if (_hasReceipt)
|
||||
ExpansionTile(
|
||||
initiallyExpanded: highlighted?.sourceBounds != null,
|
||||
title: const Text('See receipt'),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: GestureDetector(
|
||||
onTap: _openReceipt,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.file(
|
||||
File(receiptPath),
|
||||
fit: BoxFit.contain,
|
||||
height: 220,
|
||||
width: double.infinity,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: highlighted?.sourceBounds != null
|
||||
? ReceiptHighlightOverlay(
|
||||
imagePath: receiptPath!,
|
||||
bounds: highlighted!.sourceBounds!,
|
||||
)
|
||||
: GestureDetector(
|
||||
onTap: _openReceipt,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.file(
|
||||
File(receiptPath!),
|
||||
fit: BoxFit.contain,
|
||||
height: 220,
|
||||
width: double.infinity,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user