feat: Refactor inventory screens with category selection and product handling improvements
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -9,13 +9,15 @@ import '../../../core/api/api_providers.dart';
|
||||
import '../../../core/forms/form_options.dart';
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
import '../../../core/ui/category_then_product_picker.dart';
|
||||
import '../../../core/ui/product_picker_field.dart';
|
||||
import '../../../core/ui/searchable_category_field.dart';
|
||||
import '../../../core/ui/product_picker_field.dart' show ProductOption;
|
||||
import '../../../core/ui/searchable_category_field.dart' show CategorySelectOption;
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
import '../../admin/domain/admin_category_node.dart';
|
||||
import '../../pantry/data/pantry_providers.dart';
|
||||
import '../data/inventory_providers.dart';
|
||||
import '../../import/data/receipt_import_session.dart' show ImportDestination;
|
||||
import 'inventory_category_helpers.dart';
|
||||
import 'inventory_category_product_section.dart';
|
||||
|
||||
class CreateInventoryScreen extends ConsumerStatefulWidget {
|
||||
final String? initialDestination;
|
||||
@@ -41,6 +43,8 @@ class _CreateInventoryScreenState
|
||||
List<Map<String, dynamic>> _products = [];
|
||||
List<AdminCategoryNode> _categoryTree = [];
|
||||
List<CategorySelectOption> _categoryOptions = [];
|
||||
InventoryCategoryBranchIndex? _categoryBranchIndex;
|
||||
final _sortedProductsCache = InventorySortedProductsCache();
|
||||
bool _loadingProducts = false;
|
||||
DateTime? _purchaseDate;
|
||||
DateTime? _bestBeforeDate;
|
||||
@@ -95,6 +99,7 @@ class _CreateInventoryScreenState
|
||||
.map((e) => AdminCategoryNode.fromJson(Map<String, dynamic>.from(e as Map)))
|
||||
.toList();
|
||||
_categoryOptions = _flattenCategoryOptions(_categoryTree);
|
||||
_categoryBranchIndex = InventoryCategoryBranchIndex.fromTree(_categoryTree);
|
||||
_loadingProducts = false;
|
||||
});
|
||||
}
|
||||
@@ -132,11 +137,23 @@ class _CreateInventoryScreenState
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<int>? _selectedCategoryBranchIds() {
|
||||
final selectedId = _selectedCategoryId;
|
||||
if (selectedId == null) return null;
|
||||
return _categoryBranchIndex?.branchIdsFor(selectedId) ?? {selectedId};
|
||||
}
|
||||
|
||||
List<ProductOption> _productOptions() {
|
||||
final selectedCategoryIds = _selectedCategoryBranchIds();
|
||||
final source = _selectedCategoryId == null
|
||||
? _products
|
||||
: _products
|
||||
.where((p) => (p['categoryId'] as num?)?.toInt() == _selectedCategoryId)
|
||||
.where(
|
||||
(p) => productInSelectedBranch(
|
||||
productCategoryId: (p['categoryId'] as num?)?.toInt(),
|
||||
selectedCategoryIds: selectedCategoryIds,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
final selected = _selectedProduct();
|
||||
@@ -146,13 +163,8 @@ class _CreateInventoryScreenState
|
||||
withSelected.add(selected);
|
||||
}
|
||||
|
||||
withSelected.sort((a, b) {
|
||||
final aName = (a['canonicalName'] ?? a['name'] ?? '').toString().toLowerCase();
|
||||
final bName = (b['canonicalName'] ?? b['name'] ?? '').toString().toLowerCase();
|
||||
return aName.compareTo(bName);
|
||||
});
|
||||
|
||||
return withSelected
|
||||
final sorted = _sortedProductsCache.sort(withSelected);
|
||||
return sorted
|
||||
.map(
|
||||
(p) => (
|
||||
id: (p['id'] as num).toInt(),
|
||||
@@ -172,9 +184,13 @@ class _CreateInventoryScreenState
|
||||
if (selected == null || !mounted) return;
|
||||
setState(() {
|
||||
_selectedCategoryId = selected.id;
|
||||
final selectedCategoryIds = _selectedCategoryBranchIds();
|
||||
final current = _selectedProduct();
|
||||
final currentCategoryId = (current?['categoryId'] as num?)?.toInt();
|
||||
if (currentCategoryId != _selectedCategoryId) {
|
||||
if (!productInSelectedBranch(
|
||||
productCategoryId: currentCategoryId,
|
||||
selectedCategoryIds: selectedCategoryIds,
|
||||
)) {
|
||||
_selectedProductId = null;
|
||||
}
|
||||
});
|
||||
@@ -302,54 +318,41 @@ class _CreateInventoryScreenState
|
||||
: (selected) => setState(() => _destination = selected.first),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SearchableCategoryField(
|
||||
options: _categoryOptions,
|
||||
value: _selectedCategoryId?.toString(),
|
||||
label: 'Kategori (sökbar)',
|
||||
onChanged: (value) {
|
||||
InventoryCategoryProductSection(
|
||||
categoryOptions: _categoryOptions,
|
||||
selectedCategoryId: _selectedCategoryId,
|
||||
categorySearchLabel: 'Kategori (sökbar)',
|
||||
onCategoryChanged: (value) {
|
||||
if (value == null) return;
|
||||
setState(() {
|
||||
_selectedCategoryId = int.tryParse(value);
|
||||
final selectedCategoryIds = _selectedCategoryBranchIds();
|
||||
final current = _selectedProduct();
|
||||
final currentCategoryId = (current?['categoryId'] as num?)?.toInt();
|
||||
if (currentCategoryId != _selectedCategoryId) {
|
||||
if (!productInSelectedBranch(
|
||||
productCategoryId: currentCategoryId,
|
||||
selectedCategoryIds: selectedCategoryIds,
|
||||
)) {
|
||||
_selectedProductId = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: _loadingProducts || _saving || _categoryTree.isEmpty
|
||||
? null
|
||||
: _pickCategory,
|
||||
icon: const Icon(Icons.category_outlined),
|
||||
label: const Text('Välj kategori'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _saving
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_selectedCategoryId = null;
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
label: const Text('Rensa kategori'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
ProductPickerField(
|
||||
products: productOptions,
|
||||
value: _selectedProductId,
|
||||
isLoading: _loadingProducts,
|
||||
enabled: !_saving,
|
||||
label: context.l10n.inventoryProductLabel,
|
||||
onChanged: (value) => setState(() => _selectedProductId = value),
|
||||
onPickCategory: _pickCategory,
|
||||
pickCategoryLabel: 'Välj kategori',
|
||||
onClearCategory: () {
|
||||
setState(() {
|
||||
_selectedCategoryId = null;
|
||||
});
|
||||
},
|
||||
clearCategoryLabel: 'Rensa kategori',
|
||||
canPickCategory: !_loadingProducts && !_saving && _categoryTree.isNotEmpty,
|
||||
canClearCategory: !_saving,
|
||||
productOptions: productOptions,
|
||||
selectedProductId: _selectedProductId,
|
||||
onProductChanged: (value) => setState(() => _selectedProductId = value),
|
||||
isLoadingProducts: _loadingProducts,
|
||||
productEnabled: !_saving,
|
||||
productLabel: context.l10n.inventoryProductLabel,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import '../../admin/domain/admin_category_node.dart';
|
||||
|
||||
class InventoryCategoryBranchIndex {
|
||||
final Map<int, Set<int>> _descendantsById;
|
||||
|
||||
InventoryCategoryBranchIndex._(this._descendantsById);
|
||||
|
||||
factory InventoryCategoryBranchIndex.fromTree(List<AdminCategoryNode> roots) {
|
||||
final descendantsById = <int, Set<int>>{};
|
||||
|
||||
Set<int> collect(AdminCategoryNode node) {
|
||||
final ids = <int>{node.id};
|
||||
for (final child in node.children) {
|
||||
ids.addAll(collect(child));
|
||||
}
|
||||
descendantsById[node.id] = ids;
|
||||
return ids;
|
||||
}
|
||||
|
||||
for (final root in roots) {
|
||||
collect(root);
|
||||
}
|
||||
|
||||
return InventoryCategoryBranchIndex._(descendantsById);
|
||||
}
|
||||
|
||||
Set<int>? branchIdsFor(int? categoryId) {
|
||||
if (categoryId == null) return null;
|
||||
return _descendantsById[categoryId] ?? {categoryId};
|
||||
}
|
||||
}
|
||||
|
||||
bool productInSelectedBranch({
|
||||
required int? productCategoryId,
|
||||
required Set<int>? selectedCategoryIds,
|
||||
}) {
|
||||
if (selectedCategoryIds == null) return true;
|
||||
if (productCategoryId == null) return false;
|
||||
return selectedCategoryIds.contains(productCategoryId);
|
||||
}
|
||||
|
||||
class InventorySortedProductsCache {
|
||||
String? _lastKey;
|
||||
List<Map<String, dynamic>> _lastSorted = const [];
|
||||
|
||||
List<Map<String, dynamic>> sort(List<Map<String, dynamic>> products) {
|
||||
final key = _buildKey(products);
|
||||
if (_lastKey == key) return _lastSorted;
|
||||
|
||||
final sorted = [...products]
|
||||
..sort((a, b) {
|
||||
final aName = (a['canonicalName'] ?? a['name'] ?? '').toString().toLowerCase();
|
||||
final bName = (b['canonicalName'] ?? b['name'] ?? '').toString().toLowerCase();
|
||||
return aName.compareTo(bName);
|
||||
});
|
||||
|
||||
_lastKey = key;
|
||||
_lastSorted = sorted;
|
||||
return _lastSorted;
|
||||
}
|
||||
|
||||
String _buildKey(List<Map<String, dynamic>> products) {
|
||||
final b = StringBuffer('${products.length}|');
|
||||
for (final p in products) {
|
||||
final id = (p['id'] as num?)?.toInt() ?? -1;
|
||||
final categoryId = (p['categoryId'] as num?)?.toInt() ?? -1;
|
||||
final name = (p['canonicalName'] ?? p['name'] ?? '').toString();
|
||||
b
|
||||
..write(id)
|
||||
..write(':')
|
||||
..write(categoryId)
|
||||
..write(':')
|
||||
..write(name)
|
||||
..write('|');
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/ui/product_picker_field.dart' as product_picker;
|
||||
import '../../../core/ui/searchable_category_field.dart';
|
||||
|
||||
class InventoryCategoryProductSection extends StatelessWidget {
|
||||
final List<CategorySelectOption> categoryOptions;
|
||||
final int? selectedCategoryId;
|
||||
final ValueChanged<String?> onCategoryChanged;
|
||||
final String categorySearchLabel;
|
||||
final VoidCallback onPickCategory;
|
||||
final String pickCategoryLabel;
|
||||
final VoidCallback onClearCategory;
|
||||
final String clearCategoryLabel;
|
||||
final bool canPickCategory;
|
||||
final bool canClearCategory;
|
||||
final List<product_picker.ProductOption> productOptions;
|
||||
final int? selectedProductId;
|
||||
final ValueChanged<int?> onProductChanged;
|
||||
final bool isLoadingProducts;
|
||||
final bool productEnabled;
|
||||
final String productLabel;
|
||||
|
||||
const InventoryCategoryProductSection({
|
||||
super.key,
|
||||
required this.categoryOptions,
|
||||
required this.selectedCategoryId,
|
||||
required this.onCategoryChanged,
|
||||
required this.categorySearchLabel,
|
||||
required this.onPickCategory,
|
||||
required this.pickCategoryLabel,
|
||||
required this.onClearCategory,
|
||||
required this.clearCategoryLabel,
|
||||
required this.canPickCategory,
|
||||
required this.canClearCategory,
|
||||
required this.productOptions,
|
||||
required this.selectedProductId,
|
||||
required this.onProductChanged,
|
||||
required this.isLoadingProducts,
|
||||
required this.productEnabled,
|
||||
required this.productLabel,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SearchableCategoryField(
|
||||
options: categoryOptions,
|
||||
value: selectedCategoryId?.toString(),
|
||||
label: categorySearchLabel,
|
||||
onChanged: onCategoryChanged,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: canPickCategory ? onPickCategory : null,
|
||||
icon: const Icon(Icons.category_outlined),
|
||||
label: Text(pickCategoryLabel),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: canClearCategory ? onClearCategory : null,
|
||||
icon: const Icon(Icons.clear),
|
||||
label: Text(clearCategoryLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
product_picker.ProductPickerField(
|
||||
products: productOptions,
|
||||
value: selectedProductId,
|
||||
isLoading: isLoadingProducts,
|
||||
enabled: productEnabled,
|
||||
label: productLabel,
|
||||
onChanged: onProductChanged,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,14 @@ import '../../../core/forms/form_options.dart';
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
import '../../../core/ui/async_state_views.dart';
|
||||
import '../../../core/ui/category_then_product_picker.dart';
|
||||
import '../../../core/ui/product_picker_field.dart';
|
||||
import '../../../core/ui/searchable_category_field.dart';
|
||||
import '../../../core/ui/product_picker_field.dart' show ProductOption;
|
||||
import '../../../core/ui/searchable_category_field.dart' show CategorySelectOption;
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
import '../../admin/domain/admin_category_node.dart';
|
||||
import '../data/inventory_providers.dart';
|
||||
import '../domain/inventory_item.dart';
|
||||
import 'inventory_category_helpers.dart';
|
||||
import 'inventory_category_product_section.dart';
|
||||
|
||||
class InventoryEditScreen extends ConsumerStatefulWidget {
|
||||
final int itemId;
|
||||
@@ -46,6 +48,8 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
List<Map<String, dynamic>> _products = [];
|
||||
List<AdminCategoryNode> _categoryTree = [];
|
||||
List<CategorySelectOption> _categoryOptions = [];
|
||||
InventoryCategoryBranchIndex? _categoryBranchIndex;
|
||||
final _sortedProductsCache = InventorySortedProductsCache();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -113,6 +117,7 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
_products = products;
|
||||
_categoryTree = categoryTree;
|
||||
_categoryOptions = categoryOptions;
|
||||
_categoryBranchIndex = InventoryCategoryBranchIndex.fromTree(categoryTree);
|
||||
|
||||
final selected = _selectedProduct();
|
||||
if (selected != null) {
|
||||
@@ -154,10 +159,24 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<int>? _selectedCategoryBranchIds() {
|
||||
final selectedId = _selectedCategoryId;
|
||||
if (selectedId == null) return null;
|
||||
return _categoryBranchIndex?.branchIdsFor(selectedId) ?? {selectedId};
|
||||
}
|
||||
|
||||
List<ProductOption> _productOptions() {
|
||||
final selectedCategoryIds = _selectedCategoryBranchIds();
|
||||
final source = _selectedCategoryId == null
|
||||
? _products
|
||||
: _products.where((p) => (p['categoryId'] as num?)?.toInt() == _selectedCategoryId).toList();
|
||||
: _products
|
||||
.where(
|
||||
(p) => productInSelectedBranch(
|
||||
productCategoryId: (p['categoryId'] as num?)?.toInt(),
|
||||
selectedCategoryIds: selectedCategoryIds,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
final selected = _selectedProduct();
|
||||
final withSelected = [...source];
|
||||
@@ -165,13 +184,8 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
withSelected.add(selected);
|
||||
}
|
||||
|
||||
withSelected.sort((a, b) {
|
||||
final aName = (a['canonicalName'] ?? a['name'] ?? '').toString().toLowerCase();
|
||||
final bName = (b['canonicalName'] ?? b['name'] ?? '').toString().toLowerCase();
|
||||
return aName.compareTo(bName);
|
||||
});
|
||||
|
||||
return withSelected
|
||||
final sorted = _sortedProductsCache.sort(withSelected);
|
||||
return sorted
|
||||
.map(
|
||||
(p) => (
|
||||
id: (p['id'] as num).toInt(),
|
||||
@@ -191,9 +205,13 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
if (selected == null || !mounted) return;
|
||||
setState(() {
|
||||
_selectedCategoryId = selected.id;
|
||||
final selectedCategoryIds = _selectedCategoryBranchIds();
|
||||
final current = _selectedProduct();
|
||||
final currentCategoryId = (current?['categoryId'] as num?)?.toInt();
|
||||
if (currentCategoryId != _selectedCategoryId) {
|
||||
if (!productInSelectedBranch(
|
||||
productCategoryId: currentCategoryId,
|
||||
selectedCategoryIds: selectedCategoryIds,
|
||||
)) {
|
||||
_selectedProductId = null;
|
||||
}
|
||||
});
|
||||
@@ -292,54 +310,41 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
Text(item.categoryPath!, style: Theme.of(context).textTheme.bodySmall),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
SearchableCategoryField(
|
||||
options: _categoryOptions,
|
||||
value: _selectedCategoryId?.toString(),
|
||||
label: 'Kategori (sökbar)',
|
||||
onChanged: (value) {
|
||||
InventoryCategoryProductSection(
|
||||
categoryOptions: _categoryOptions,
|
||||
selectedCategoryId: _selectedCategoryId,
|
||||
categorySearchLabel: 'Kategori (sökbar)',
|
||||
onCategoryChanged: (value) {
|
||||
if (value == null) return;
|
||||
setState(() {
|
||||
_selectedCategoryId = int.tryParse(value);
|
||||
final selectedCategoryIds = _selectedCategoryBranchIds();
|
||||
final current = _selectedProduct();
|
||||
final currentCategoryId = (current?['categoryId'] as num?)?.toInt();
|
||||
if (currentCategoryId != _selectedCategoryId) {
|
||||
if (!productInSelectedBranch(
|
||||
productCategoryId: currentCategoryId,
|
||||
selectedCategoryIds: selectedCategoryIds,
|
||||
)) {
|
||||
_selectedProductId = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: _loadingProducts || _saving || _categoryTree.isEmpty
|
||||
? null
|
||||
: _pickCategory,
|
||||
icon: const Icon(Icons.category_outlined),
|
||||
label: const Text('Välj kategori'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _saving
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_selectedCategoryId = null;
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
label: const Text('Rensa kategori'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
ProductPickerField(
|
||||
products: _productOptions(),
|
||||
value: _selectedProductId,
|
||||
isLoading: _loadingProducts,
|
||||
enabled: !_saving,
|
||||
label: context.l10n.inventoryProductLabel,
|
||||
onChanged: (value) => setState(() => _selectedProductId = value),
|
||||
onPickCategory: _pickCategory,
|
||||
pickCategoryLabel: 'Välj kategori',
|
||||
onClearCategory: () {
|
||||
setState(() {
|
||||
_selectedCategoryId = null;
|
||||
});
|
||||
},
|
||||
clearCategoryLabel: 'Rensa kategori',
|
||||
canPickCategory: !_loadingProducts && !_saving && _categoryTree.isNotEmpty,
|
||||
canClearCategory: !_saving,
|
||||
productOptions: _productOptions(),
|
||||
selectedProductId: _selectedProductId,
|
||||
onProductChanged: (value) => setState(() => _selectedProductId = value),
|
||||
isLoadingProducts: _loadingProducts,
|
||||
productEnabled: !_saving,
|
||||
productLabel: context.l10n.inventoryProductLabel,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../core/api/api_error_mapper.dart';
|
||||
import '../../../core/utils/display_labels.dart';
|
||||
import '../../../core/utils/formatters.dart';
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
import '../data/inventory_providers.dart';
|
||||
@@ -286,12 +287,11 @@ class _ForegroundTile extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final location = item.location?.trim();
|
||||
final hasLocation = location != null && location.isNotEmpty;
|
||||
final location = normalizedOptionalText(item.location);
|
||||
|
||||
final subtitleText = [
|
||||
'${_fmtQty(item.quantity)} ${item.unit}',
|
||||
if (hasLocation) location,
|
||||
if (location != null) location,
|
||||
if (item.bestBeforeDate != null)
|
||||
'Bäst före: ${_formatDate(item.bestBeforeDate!)}',
|
||||
].join(' · ');
|
||||
@@ -331,26 +331,23 @@ class _ForegroundTile extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Chip(
|
||||
label: Text(
|
||||
'L1: ${item.l1Category}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide(color: theme.colorScheme.outlineVariant),
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
labelStyle: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Chip(
|
||||
label: Text(
|
||||
l1CategoryChipLabel('L1: ', item.l1Category),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide(color: theme.colorScheme.outlineVariant),
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
labelStyle: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user