refactor: Remove PantryProduct class and simplify category resolution in PantryScreen
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:
@@ -4,7 +4,6 @@ import '../../../core/api/api_providers.dart';
|
||||
import '../../../core/api/guarded_api_call.dart';
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
import '../domain/pantry_item.dart';
|
||||
import '../domain/pantry_product.dart';
|
||||
import 'pantry_repository.dart';
|
||||
|
||||
final pantryRepositoryProvider = Provider<PantryRepository>((ref) {
|
||||
@@ -18,11 +17,3 @@ final pantryProvider = FutureProvider<List<PantryItem>>((ref) async {
|
||||
() => ref.read(pantryRepositoryProvider).fetchPantry(token: token),
|
||||
);
|
||||
});
|
||||
|
||||
final pantryProductsProvider = FutureProvider<List<PantryProduct>>((ref) async {
|
||||
final token = await ref.watch(authStateProvider.future);
|
||||
return guardedApiCall(
|
||||
ref,
|
||||
() => ref.read(pantryRepositoryProvider).fetchProducts(token: token),
|
||||
);
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import 'package:logging/logging.dart';
|
||||
import '../../../core/api/api_client.dart';
|
||||
import '../../../core/api/api_paths.dart';
|
||||
import '../domain/pantry_item.dart';
|
||||
import '../domain/pantry_product.dart';
|
||||
|
||||
final _logger = Logger('PantryRepository');
|
||||
|
||||
@@ -25,20 +24,6 @@ class PantryRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PantryProduct>> fetchProducts({String? token}) async {
|
||||
try {
|
||||
final data = await _api.getJson(ProductApiPaths.mine, token: token);
|
||||
final list = data as List<dynamic>;
|
||||
_logger.info('Fetched ${list.length} products');
|
||||
return list
|
||||
.map((e) => PantryProduct.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
} catch (error) {
|
||||
_logger.severe('Failed to fetch products: $error');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<PantryItem> createPantryItem(
|
||||
int productId, {
|
||||
String? token,
|
||||
|
||||
@@ -5,6 +5,7 @@ class PantryItem {
|
||||
final String? canonicalName;
|
||||
final String? category;
|
||||
final int? categoryId;
|
||||
final String? categoryPath;
|
||||
final String? location;
|
||||
|
||||
const PantryItem({
|
||||
@@ -14,6 +15,7 @@ class PantryItem {
|
||||
this.canonicalName,
|
||||
this.category,
|
||||
this.categoryId,
|
||||
this.categoryPath,
|
||||
this.location,
|
||||
});
|
||||
|
||||
@@ -24,6 +26,17 @@ class PantryItem {
|
||||
return productName;
|
||||
}
|
||||
|
||||
String? get l1CategoryOrNull {
|
||||
final path = categoryPath?.trim();
|
||||
if (path != null && path.isNotEmpty) {
|
||||
return path.split('>').first.trim();
|
||||
}
|
||||
if (category != null && category!.trim().isNotEmpty) {
|
||||
return category!.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
factory PantryItem.fromJson(Map<String, dynamic> json) {
|
||||
final product = json['product'] as Map<String, dynamic>? ?? {};
|
||||
return PantryItem(
|
||||
@@ -33,7 +46,25 @@ class PantryItem {
|
||||
canonicalName: product['canonicalName']?.toString(),
|
||||
category: product['category']?.toString(),
|
||||
categoryId: (product['categoryId'] as num?)?.toInt(),
|
||||
categoryPath: _buildCategoryPath(product['categoryRef']),
|
||||
location: json['location']?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
static String? _buildCategoryPath(dynamic rawCategoryRef) {
|
||||
if (rawCategoryRef is! Map<String, dynamic>) return null;
|
||||
|
||||
final names = <String>[];
|
||||
dynamic current = rawCategoryRef;
|
||||
while (current is Map<String, dynamic>) {
|
||||
final name = current['name']?.toString().trim();
|
||||
if (name != null && name.isNotEmpty) {
|
||||
names.add(name);
|
||||
}
|
||||
current = current['parent'];
|
||||
}
|
||||
|
||||
if (names.isEmpty) return null;
|
||||
return names.reversed.join(' > ');
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
class PantryProduct {
|
||||
final int id;
|
||||
final String name;
|
||||
final String? canonicalName;
|
||||
final String? category;
|
||||
final int? categoryId;
|
||||
final String? categoryPath;
|
||||
|
||||
const PantryProduct({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.canonicalName,
|
||||
this.category,
|
||||
this.categoryId,
|
||||
this.categoryPath,
|
||||
});
|
||||
|
||||
String get displayName {
|
||||
if (canonicalName != null && canonicalName!.trim().isNotEmpty) {
|
||||
return canonicalName!;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
factory PantryProduct.fromJson(Map<String, dynamic> json) {
|
||||
final categoryRef = json['categoryRef'];
|
||||
final path = _buildCategoryPath(categoryRef);
|
||||
|
||||
return PantryProduct(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: (json['name'] ?? '').toString(),
|
||||
canonicalName: json['canonicalName']?.toString(),
|
||||
category: json['category']?.toString(),
|
||||
categoryId: (json['categoryId'] as num?)?.toInt(),
|
||||
categoryPath: path,
|
||||
);
|
||||
}
|
||||
|
||||
static String? _buildCategoryPath(dynamic rawCategoryRef) {
|
||||
if (rawCategoryRef is! Map<String, dynamic>) return null;
|
||||
|
||||
final names = <String>[];
|
||||
dynamic current = rawCategoryRef;
|
||||
while (current is Map<String, dynamic>) {
|
||||
final name = current['name']?.toString().trim();
|
||||
if (name != null && name.isNotEmpty) {
|
||||
names.insert(0, name);
|
||||
}
|
||||
current = current['parent'];
|
||||
}
|
||||
|
||||
if (names.isEmpty) return null;
|
||||
return names.join(' > ');
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import '../../auth/data/auth_providers.dart';
|
||||
import '../../inventory/data/inventory_providers.dart';
|
||||
import '../data/pantry_providers.dart';
|
||||
import '../domain/pantry_item.dart';
|
||||
import '../domain/pantry_product.dart';
|
||||
|
||||
final _logger = Logger('PantryScreen');
|
||||
|
||||
@@ -214,35 +213,26 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
String _resolveL1Category(PantryItem item, Map<int, PantryProduct> productById) {
|
||||
final path = productById[item.productId]?.categoryPath?.trim();
|
||||
if (path != null && path.isNotEmpty) {
|
||||
return path.split('>').first.trim();
|
||||
}
|
||||
if (item.category != null && item.category!.trim().isNotEmpty) {
|
||||
return item.category!.trim();
|
||||
}
|
||||
return context.l10n.pantryOtherCategory;
|
||||
String _resolveL1Category(PantryItem item) {
|
||||
return item.l1CategoryOrNull ?? context.l10n.pantryOtherCategory;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pantryAsync = ref.watch(pantryProvider);
|
||||
final productsAsync = ref.watch(pantryProductsProvider);
|
||||
|
||||
if (pantryAsync.isLoading || productsAsync.isLoading) {
|
||||
if (pantryAsync.isLoading) {
|
||||
return LoadingStateView(label: context.l10n.pantryLoading);
|
||||
}
|
||||
|
||||
if (pantryAsync.hasError || productsAsync.hasError) {
|
||||
final error = pantryAsync.error ?? productsAsync.error;
|
||||
if (pantryAsync.hasError) {
|
||||
final error = pantryAsync.error;
|
||||
_logger.severe('Error loading pantry or products: $error');
|
||||
return buildCopyableErrorPanel(
|
||||
context: context,
|
||||
message: mapErrorToUserMessage(error ?? 'Okänt fel', context),
|
||||
onRetry: () {
|
||||
ref.invalidate(pantryProvider);
|
||||
ref.invalidate(pantryProductsProvider);
|
||||
},
|
||||
title: 'Kunde inte läsa baslagret',
|
||||
);
|
||||
@@ -250,15 +240,17 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
||||
|
||||
final pantryItems =
|
||||
pantryAsync.maybeWhen(data: (d) => d, orElse: () => null) ?? const [];
|
||||
final products =
|
||||
productsAsync.maybeWhen(data: (d) => d, orElse: () => null) ?? const [];
|
||||
final productById = {for (final product in products) product.id: product};
|
||||
|
||||
final filteredItems = pantryItems.where((item) {
|
||||
if (_locationFilter.isEmpty) return true;
|
||||
return (item.location ?? '').trim() == _locationFilter;
|
||||
}).toList();
|
||||
|
||||
final l1LowerByItemId = {
|
||||
for (final item in filteredItems)
|
||||
item.id: _resolveL1Category(item).toLowerCase(),
|
||||
};
|
||||
|
||||
filteredItems.sort((a, b) {
|
||||
if (_sort == 'nameDesc') {
|
||||
return b.displayName.toLowerCase().compareTo(a.displayName.toLowerCase());
|
||||
@@ -271,9 +263,7 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
||||
return a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase());
|
||||
}
|
||||
if (_sort == 'l1CategoryAsc') {
|
||||
final byL1 = _resolveL1Category(a, productById).toLowerCase().compareTo(
|
||||
_resolveL1Category(b, productById).toLowerCase(),
|
||||
);
|
||||
final byL1 = (l1LowerByItemId[a.id] ?? '').compareTo(l1LowerByItemId[b.id] ?? '');
|
||||
if (byL1 != 0) return byL1;
|
||||
return a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase());
|
||||
}
|
||||
@@ -379,7 +369,7 @@ class _PantryScreenState extends ConsumerState<PantryScreen> {
|
||||
if (index == 0) return filterSection;
|
||||
if (index == 1) return headerSection;
|
||||
final item = filteredItems[index - 2];
|
||||
final l1Category = _resolveL1Category(item, productById);
|
||||
final l1Category = _resolveL1Category(item);
|
||||
|
||||
return ListTile(
|
||||
title: Text(item.displayName),
|
||||
|
||||
Reference in New Issue
Block a user