chore: update flyer import features and resources
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 2m43s
Test Suite / flutter-quality (push) Failing after 1m33s

- Remove outdated Willys flyer PDF (0001-0008_WIL_V21_ED1pdf.pdf)
- Add new Willys flyer PDF (willys_reklamblad.pdf)
- Improve offer detection logic in backend flyer-import service
- Add offer limit text extraction and sanitization in Flutter UI
- Fix Swedish character encoding issues in UI text
This commit is contained in:
Nils-Johan Gynther
2026-05-18 23:40:05 +02:00
parent c720f611ea
commit 0ce1db5471
3 changed files with 159 additions and 51 deletions
@@ -116,6 +116,17 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
return '$raw kr$unitPart';
}
String _removeLimitTextFromOfferText(String offerText, String? limitText) {
final trimmedOffer = offerText.trim();
final trimmedLimit = limitText?.trim() ?? '';
if (trimmedOffer.isEmpty || trimmedLimit.isEmpty) return trimmedOffer;
final escaped = RegExp.escape(trimmedLimit);
final withoutLimit = trimmedOffer.replaceAll(RegExp(escaped, caseSensitive: false), '').trim();
final withoutLeadingPunctuation = withoutLimit.replaceAll(RegExp(r'^[,.;:\s-]+'), '').trim();
return withoutLeadingPunctuation.replaceAll(RegExp(r'[,.;:\s-]+$'), '').trim();
}
Widget _buildOfferBadge(FlyerImportItem item, ThemeData theme) {
final hasOffer = item.isOffer || (item.offerText?.trim().isNotEmpty ?? false) || item.price != null;
if (!hasOffer) return const SizedBox.shrink();
@@ -157,7 +168,7 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
isImage ? Icons.image_outlined : Icons.picture_as_pdf_outlined,
color: theme.colorScheme.primary,
),
title: const Text('Flyerförhandsvisning'),
title: const Text('Flyerförhandsvisning'),
subtitle: Text(file?.name ?? ''),
trailing: isImage
? null
@@ -170,7 +181,7 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
if (!context.mounted || opened) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('PDF kan bara öppnas direkt i webbversionen just nu.'),
content: Text('PDF kan bara öppnas direkt i webbversionen just nu.'),
),
);
},
@@ -206,14 +217,14 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Ladda upp flyer, granska erbjudanden och planera inköp med ett klick.',
'Ladda upp flyer, granska erbjudanden och planera inköp med ett klick.',
style: theme.textTheme.bodyMedium,
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: _isLoading ? null : _pickFile,
icon: const Icon(Icons.attach_file),
label: Text(_pickedFile?.name ?? 'Välj flyerfil'),
label: Text(_pickedFile?.name ?? 'Välj flyerfil'),
),
const SizedBox(height: 12),
FilledButton.icon(
@@ -242,7 +253,7 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
}
});
},
child: Text(selectedCount < items.length ? 'Välj alla' : 'Avmarkera alla'),
child: Text(selectedCount < items.length ? 'Välj alla' : 'Avmarkera alla'),
),
],
),
@@ -253,6 +264,9 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
final priceText = _formatPrice(item.price, item.priceUnit);
final comparisonText = _formatPrice(item.comparisonPrice, item.comparisonUnit);
final limitText = item.offerLimitText?.trim();
final sanitizedOfferText = item.offerText == null
? ''
: _removeLimitTextFromOfferText(item.offerText!, limitText);
return CheckboxListTile(
value: _selected[index] ?? false,
@@ -267,9 +281,16 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (priceText.isNotEmpty) Text('Pris: $priceText'),
if (comparisonText.isNotEmpty) Text('Jämförpris: $comparisonText'),
if (limitText != null && limitText.isNotEmpty) Text('Begränsning: $limitText'),
if ((item.offerText?.trim().isNotEmpty ?? false)) Text(item.offerText!.trim()),
if (comparisonText.isNotEmpty) Text('Jämförpris: $comparisonText'),
if (limitText != null && limitText.isNotEmpty)
Text(
'Begränsning: $limitText',
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.orange.shade900,
fontWeight: FontWeight.w600,
),
),
if (sanitizedOfferText.isNotEmpty) Text(sanitizedOfferText),
if (item.matchedProductName != null) Text('Match: ${item.matchedProductName}'),
],
),
@@ -296,5 +317,4 @@ class _FlyerImportTabState extends ConsumerState<FlyerImportTab> {
),
);
}
}
}