feat: Refactor routing and navigation structure with StatefulShellRoute
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
- Introduced a new function `_shellBranchIndexForPath` to determine the index of the shell branch based on the path. - Replaced `ShellRoute` with `StatefulShellRoute.indexedStack` for better state management during navigation. - Updated `AppShell` to handle navigation path changes and integrate with the new routing structure. - Organized routes into `StatefulShellBranch` for better modularity and clarity. - Enhanced admin panel functionality with improved alias management and UI updates. - Added new methods in `ReceiptImportSessionNotifier` for managing selected items and edits more efficiently. - Improved UI components in receipt import and admin panels for better performance and user experience. - Added PageStorageKeys to various ListViews to maintain scroll positions across navigation. - Documented performance goals and profiling strategies in a new PERFORMANCE.md file.
This commit is contained in:
@@ -292,12 +292,13 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
);
|
||||
if (!mounted) return;
|
||||
final notifier = ref.read(receiptImportSessionProvider.notifier);
|
||||
notifier.setItems(items);
|
||||
final nextEdits = <int, _ItemEdit>{};
|
||||
final nextSelected = <int, bool>{};
|
||||
// Förmarkera rader som har en träff
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
final it = items[i];
|
||||
final pid = it.matchedProductId ?? it.suggestedProductId;
|
||||
notifier.setSelected(i, pid != null);
|
||||
nextSelected[i] = pid != null;
|
||||
if (pid != null) {
|
||||
final inferred = inferPackageFields(
|
||||
rawName: it.rawName,
|
||||
@@ -308,7 +309,7 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
final resolvedCategoryId = it.categorySuggestionId ?? _categoryIdForProduct(pid);
|
||||
final resolvedCategoryPath = it.categorySuggestionPath ??
|
||||
_lookup.pathFor(resolvedCategoryId);
|
||||
notifier.setEdit(i, _ItemEdit(
|
||||
nextEdits[i] = _ItemEdit(
|
||||
productId: pid,
|
||||
productName: name,
|
||||
categoryId: resolvedCategoryId,
|
||||
@@ -321,9 +322,14 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
packQuantity: inferred.packQuantity,
|
||||
packUnit: inferred.packUnit,
|
||||
packageCount: inferred.packageCount,
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
notifier.setImportedResult(
|
||||
items: items,
|
||||
edits: nextEdits,
|
||||
selected: nextSelected,
|
||||
);
|
||||
// Ladda inventariet för att visa befintliga poster och möjliggöra sammanslagning
|
||||
await _loadInventory();
|
||||
} catch (e) {
|
||||
@@ -573,7 +579,7 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
);
|
||||
// Avmarkera sparade rader och uppdatera inventariet
|
||||
final notifier = ref.read(receiptImportSessionProvider.notifier);
|
||||
for (final i in toAdd) notifier.setSelected(i, false);
|
||||
notifier.setSelectedForIndexes(toAdd, false);
|
||||
setState(() {});
|
||||
await _loadInventory();
|
||||
} catch (e) {
|
||||
@@ -678,6 +684,9 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
final selectedFileName = _pickedFile?.name ?? session?.fileName;
|
||||
final selectedFileSizeBytes =
|
||||
_pickedFile?.size ?? session?.fileBytes?.length;
|
||||
final resultListHeight = items == null
|
||||
? 0.0
|
||||
: (items.length * 128.0).clamp(220.0, 620.0).toDouble();
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -736,211 +745,42 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
TextButton(
|
||||
onPressed: () => setState(() {
|
||||
final notifier = ref.read(receiptImportSessionProvider.notifier);
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
notifier.setSelected(i, _selectedCount < items.length);
|
||||
}
|
||||
notifier.setSelectedForAll(items.length, _selectedCount < items.length);
|
||||
}),
|
||||
child: Text(_selectedCount < items.length ? 'Välj alla' : 'Avmarkera alla'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
...List.generate(items.length, (i) {
|
||||
final item = items[i];
|
||||
final edit = _edits[i];
|
||||
final isChecked = _selected[i] ?? false;
|
||||
final hasProduct = edit?.productId != null;
|
||||
final isMatched = item.matchedProductId != null;
|
||||
final isSuggested = item.suggestedProductId != null && item.matchedProductId == null;
|
||||
final existingInv = edit?.productId != null && edit?.destination != _Destination.pantry
|
||||
? _inventoryByProduct[edit!.productId]
|
||||
: null;
|
||||
final inferredForPreview = inferPackageFields(
|
||||
rawName: item.rawName,
|
||||
quantity: edit?.quantity ?? item.quantity,
|
||||
unit: edit?.unit ?? item.unit,
|
||||
);
|
||||
final previewPackageCount = edit?.packageCount ?? inferredForPreview.packageCount;
|
||||
final previewPackQuantity = edit?.packQuantity ?? inferredForPreview.packQuantity;
|
||||
final previewIncomingQty = previewPackQuantity != null
|
||||
? (previewPackQuantity * previewPackageCount)
|
||||
: (edit?.quantity ?? inferredForPreview.totalQuantity ?? item.quantity ?? 0);
|
||||
final previewIncomingUnit = edit?.packUnit ??
|
||||
inferredForPreview.packUnit ??
|
||||
edit?.unit ??
|
||||
item.unit ??
|
||||
'st';
|
||||
final convertedPreviewQty = existingInv == null
|
||||
? null
|
||||
: convertQuantity(
|
||||
previewIncomingQty,
|
||||
previewIncomingUnit,
|
||||
existingInv.unit,
|
||||
);
|
||||
final canMergePreview = existingInv != null && convertedPreviewQty != null;
|
||||
final alreadyInPantry = edit?.productId != null && edit?.destination == _Destination.pantry
|
||||
? _pantryProductIds.contains(edit!.productId)
|
||||
: false;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: ListTile(
|
||||
leading: Checkbox(
|
||||
value: isChecked,
|
||||
onChanged: (v) {
|
||||
ref.read(receiptImportSessionProvider.notifier).setSelected(i, v ?? false);
|
||||
setState(() {});
|
||||
SizedBox(
|
||||
height: resultListHeight,
|
||||
child: ListView.builder(
|
||||
key: const PageStorageKey<String>('receipt-import-result-list'),
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, i) {
|
||||
return _ReceiptImportResultRow(
|
||||
index: i,
|
||||
item: items[i],
|
||||
edit: _edits[i],
|
||||
existingInventoryByProduct: _inventoryByProduct,
|
||||
pantryProductIds: _pantryProductIds,
|
||||
onCheckedChanged: (v) {
|
||||
ref.read(receiptImportSessionProvider.notifier).setSelected(i, v);
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
normalizeProductName(item.rawName),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
[
|
||||
if ((edit?.quantity ?? item.quantity) != null)
|
||||
'${edit?.quantity ?? item.quantity}',
|
||||
if ((edit?.unit ?? item.unit) != null)
|
||||
edit?.unit ?? item.unit!,
|
||||
if (item.price != null) '· ${item.price} kr',
|
||||
].join(' '),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
if (hasProduct)
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Produktnamn: ${normalizeProductName(edit!.productName ?? '')}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: isMatched ? Colors.green.shade700 : theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
_buildMatchedViaBadge(item, theme),
|
||||
if (edit.categorySource != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: edit.categorySource == CategorySelectionSource.ai
|
||||
? Colors.green.shade50
|
||||
: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(
|
||||
color: edit.categorySource == CategorySelectionSource.ai
|
||||
? Colors.green.shade300
|
||||
: theme.colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
edit.categorySource == CategorySelectionSource.ai ? 'AI' : 'Manuell',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: edit.categorySource == CategorySelectionSource.ai
|
||||
? Colors.green.shade800
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else if (isSuggested)
|
||||
Text('Namnförslag: ${normalizeProductName(item.suggestedProductName ?? '')}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: Colors.orange.shade700))
|
||||
else
|
||||
Text('Ingen matchning ännu — tryck för att välja eller skapa produkt',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.tertiary)),
|
||||
if (hasProduct && edit?.categoryPath != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Kategori: ${edit!.categoryPath!}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!hasProduct && !isSuggested) ...[
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _openEditDialog(
|
||||
i,
|
||||
initialEntryMode: ImportProductEntryMode.existing,
|
||||
),
|
||||
icon: const Icon(Icons.search, size: 16),
|
||||
label: const Text('Välj befintlig'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _openEditDialog(
|
||||
i,
|
||||
initialEntryMode: ImportProductEntryMode.create,
|
||||
),
|
||||
icon: const Icon(Icons.add_box_outlined, size: 16),
|
||||
label: const Text('Ny produkt'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (existingInv != null && canMergePreview) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(children: [
|
||||
Icon(Icons.kitchen_outlined, size: 12, color: Colors.blue.shade700),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'I lager: ${existingInv.quantity} ${existingInv.unit} → blir ${(existingInv.quantity + (convertedPreviewQty ?? 0)).toStringAsFixed(existingInv.quantity % 1 == 0 ? 0 : 2)} ${existingInv.unit}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: Colors.blue.shade700),
|
||||
),
|
||||
]),
|
||||
],
|
||||
if (existingInv != null && !canMergePreview) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(children: [
|
||||
Icon(Icons.info_outline, size: 12, color: Colors.orange.shade700),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'Finns i lager med annan enhet (${existingInv.unit}) - sparas som ny rad',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: Colors.orange.shade700),
|
||||
),
|
||||
]),
|
||||
],
|
||||
if (alreadyInPantry) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(children: [
|
||||
Icon(Icons.inventory_2_outlined, size: 12, color: Colors.orange.shade700),
|
||||
const SizedBox(width: 3),
|
||||
Text('Finns redan i baslager',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: Colors.orange.shade700)),
|
||||
]),
|
||||
],
|
||||
],
|
||||
),
|
||||
trailing: Icon(
|
||||
hasProduct ? Icons.check_circle : (isSuggested ? Icons.help_outline : Icons.error_outline),
|
||||
color: hasProduct
|
||||
? Colors.green
|
||||
: (isSuggested ? Colors.orange : theme.colorScheme.tertiary),
|
||||
size: 20,
|
||||
),
|
||||
onTap: () => _openEditDialog(i),
|
||||
),
|
||||
);
|
||||
}),
|
||||
onEditRequested: () => _openEditDialog(i),
|
||||
onSelectExistingRequested: () => _openEditDialog(
|
||||
i,
|
||||
initialEntryMode: ImportProductEntryMode.existing,
|
||||
),
|
||||
onCreateRequested: () => _openEditDialog(
|
||||
i,
|
||||
initialEntryMode: ImportProductEntryMode.create,
|
||||
),
|
||||
matchedViaBadgeBuilder: _buildMatchedViaBadge,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
@@ -959,3 +799,235 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
}
|
||||
}
|
||||
|
||||
class _ReceiptImportResultRow extends ConsumerWidget {
|
||||
final int index;
|
||||
final ParsedReceiptItem item;
|
||||
final _ItemEdit? edit;
|
||||
final Map<int, InventoryItem> existingInventoryByProduct;
|
||||
final Set<int> pantryProductIds;
|
||||
final ValueChanged<bool> onCheckedChanged;
|
||||
final VoidCallback onEditRequested;
|
||||
final VoidCallback onSelectExistingRequested;
|
||||
final VoidCallback onCreateRequested;
|
||||
final Widget Function(ParsedReceiptItem item, ThemeData theme)
|
||||
matchedViaBadgeBuilder;
|
||||
|
||||
const _ReceiptImportResultRow({
|
||||
required this.index,
|
||||
required this.item,
|
||||
required this.edit,
|
||||
required this.existingInventoryByProduct,
|
||||
required this.pantryProductIds,
|
||||
required this.onCheckedChanged,
|
||||
required this.onEditRequested,
|
||||
required this.onSelectExistingRequested,
|
||||
required this.onCreateRequested,
|
||||
required this.matchedViaBadgeBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isChecked = ref.watch(
|
||||
receiptImportSessionProvider.select((s) => s?.selected[index] ?? false),
|
||||
);
|
||||
final theme = Theme.of(context);
|
||||
final hasProduct = edit?.productId != null;
|
||||
final isMatched = item.matchedProductId != null;
|
||||
final isSuggested =
|
||||
item.suggestedProductId != null && item.matchedProductId == null;
|
||||
final existingInv = edit?.productId != null && edit?.destination != _Destination.pantry
|
||||
? existingInventoryByProduct[edit!.productId]
|
||||
: null;
|
||||
final inferredForPreview = inferPackageFields(
|
||||
rawName: item.rawName,
|
||||
quantity: edit?.quantity ?? item.quantity,
|
||||
unit: edit?.unit ?? item.unit,
|
||||
);
|
||||
final previewPackageCount =
|
||||
edit?.packageCount ?? inferredForPreview.packageCount;
|
||||
final previewPackQuantity =
|
||||
edit?.packQuantity ?? inferredForPreview.packQuantity;
|
||||
final previewIncomingQty = previewPackQuantity != null
|
||||
? (previewPackQuantity * previewPackageCount)
|
||||
: (edit?.quantity ?? inferredForPreview.totalQuantity ?? item.quantity ?? 0);
|
||||
final previewIncomingUnit = edit?.packUnit ??
|
||||
inferredForPreview.packUnit ??
|
||||
edit?.unit ??
|
||||
item.unit ??
|
||||
'st';
|
||||
final convertedPreviewQty = existingInv == null
|
||||
? null
|
||||
: convertQuantity(
|
||||
previewIncomingQty,
|
||||
previewIncomingUnit,
|
||||
existingInv.unit,
|
||||
);
|
||||
final canMergePreview = existingInv != null && convertedPreviewQty != null;
|
||||
final alreadyInPantry = edit?.productId != null && edit?.destination == _Destination.pantry
|
||||
? pantryProductIds.contains(edit!.productId)
|
||||
: false;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 3),
|
||||
child: ListTile(
|
||||
leading: Checkbox(
|
||||
value: isChecked,
|
||||
onChanged: (v) => onCheckedChanged(v ?? false),
|
||||
),
|
||||
title: Text(
|
||||
normalizeProductName(item.rawName),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
[
|
||||
if ((edit?.quantity ?? item.quantity) != null)
|
||||
'${edit?.quantity ?? item.quantity}',
|
||||
if ((edit?.unit ?? item.unit) != null) edit?.unit ?? item.unit!,
|
||||
if (item.price != null) '· ${item.price} kr',
|
||||
].join(' '),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
if (hasProduct)
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Produktnamn: ${normalizeProductName(edit!.productName ?? '')}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color:
|
||||
isMatched ? Colors.green.shade700 : theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
matchedViaBadgeBuilder(item, theme),
|
||||
if (edit!.categorySource != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: edit!.categorySource == CategorySelectionSource.ai
|
||||
? Colors.green.shade50
|
||||
: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(
|
||||
color: edit!.categorySource == CategorySelectionSource.ai
|
||||
? Colors.green.shade300
|
||||
: theme.colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
edit!.categorySource == CategorySelectionSource.ai
|
||||
? 'AI'
|
||||
: 'Manuell',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: edit!.categorySource == CategorySelectionSource.ai
|
||||
? Colors.green.shade800
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else if (isSuggested)
|
||||
Text(
|
||||
'Namnförslag: ${normalizeProductName(item.suggestedProductName ?? '')}',
|
||||
style: theme.textTheme.bodySmall
|
||||
?.copyWith(color: Colors.orange.shade700),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
'Ingen matchning ännu — tryck för att välja eller skapa produkt',
|
||||
style: theme.textTheme.bodySmall
|
||||
?.copyWith(color: theme.colorScheme.tertiary),
|
||||
),
|
||||
if (hasProduct && edit?.categoryPath != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Kategori: ${edit!.categoryPath!}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!hasProduct && !isSuggested) ...[
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: onSelectExistingRequested,
|
||||
icon: const Icon(Icons.search, size: 16),
|
||||
label: const Text('Välj befintlig'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: onCreateRequested,
|
||||
icon: const Icon(Icons.add_box_outlined, size: 16),
|
||||
label: const Text('Ny produkt'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (existingInv != null && canMergePreview) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(children: [
|
||||
Icon(Icons.kitchen_outlined, size: 12, color: Colors.blue.shade700),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'I lager: ${existingInv.quantity} ${existingInv.unit} → blir ${(existingInv.quantity + (convertedPreviewQty ?? 0)).toStringAsFixed(existingInv.quantity % 1 == 0 ? 0 : 2)} ${existingInv.unit}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: Colors.blue.shade700),
|
||||
),
|
||||
]),
|
||||
],
|
||||
if (existingInv != null && !canMergePreview) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(children: [
|
||||
Icon(Icons.info_outline, size: 12, color: Colors.orange.shade700),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'Finns i lager med annan enhet (${existingInv.unit}) - sparas som ny rad',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: Colors.orange.shade700),
|
||||
),
|
||||
]),
|
||||
],
|
||||
if (alreadyInPantry) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(children: [
|
||||
Icon(Icons.inventory_2_outlined, size: 12, color: Colors.orange.shade700),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'Finns redan i baslager',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: Colors.orange.shade700),
|
||||
),
|
||||
]),
|
||||
],
|
||||
],
|
||||
),
|
||||
trailing: Icon(
|
||||
hasProduct
|
||||
? Icons.check_circle
|
||||
: (isSuggested ? Icons.help_outline : Icons.error_outline),
|
||||
color: hasProduct
|
||||
? Colors.green
|
||||
: (isSuggested ? Colors.orange : theme.colorScheme.tertiary),
|
||||
size: 20,
|
||||
),
|
||||
onTap: onEditRequested,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user