import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../data/variety_repository.dart'; import '../db/enums.dart'; /// Inventory list state: all items from the DB plus the current search query /// and active filters. [visibleItems] applies query ∧ category ∧ form; grouping /// by category is done in the UI. Empty filter sets mean "show everything". class InventoryState extends Equatable { const InventoryState({ this.items = const [], this.query = '', this.categoryFilter = const {}, this.typeFilter = const {}, this.loading = true, }); final List items; final String query; /// Categories to keep; empty = all categories. final Set categoryFilter; /// Lot forms to keep; empty = all forms. An item matches if it holds at /// least one lot of a selected form. final Set typeFilter; final bool loading; /// Categories present across all items, in display order (deduped), so the /// UI can offer one chip per category actually in use. List get categories { final seen = {}; final result = []; for (final item in items) { final category = item.category; if (category != null && seen.add(category)) result.add(category); } return result; } List get visibleItems { final q = query.trim().toLowerCase(); return items.where((i) { if (q.isNotEmpty && !i.label.toLowerCase().contains(q)) return false; if (categoryFilter.isNotEmpty && !categoryFilter.contains(i.category)) { return false; } if (typeFilter.isNotEmpty && i.lotTypes.intersection(typeFilter).isEmpty) { return false; } return true; }).toList(); } InventoryState copyWith({ List? items, String? query, Set? categoryFilter, Set? typeFilter, bool? loading, }) { return InventoryState( items: items ?? this.items, query: query ?? this.query, categoryFilter: categoryFilter ?? this.categoryFilter, typeFilter: typeFilter ?? this.typeFilter, loading: loading ?? this.loading, ); } @override List get props => [ items, query, categoryFilter, typeFilter, loading, ]; } /// Subscribes to the repository's reactive inventory stream. The list updates /// automatically after a quick-add — no manual refresh. class InventoryCubit extends Cubit { InventoryCubit(this._repo) : super(const InventoryState()) { _sub = _repo.watchInventory().listen( (items) => emit(state.copyWith(items: items, loading: false)), ); } final VarietyRepository _repo; late final StreamSubscription> _sub; void search(String query) => emit(state.copyWith(query: query)); /// Toggles a category in the filter (add if absent, remove if present). void toggleCategory(String category) { final next = Set.of(state.categoryFilter); if (!next.remove(category)) next.add(category); emit(state.copyWith(categoryFilter: next)); } /// Toggles a lot form in the filter (add if absent, remove if present). void toggleType(LotType type) { final next = Set.of(state.typeFilter); if (!next.remove(type)) next.add(type); emit(state.copyWith(typeFilter: next)); } /// Clears both filters (search is left untouched). void clearFilters() => emit(state.copyWith(categoryFilter: const {}, typeFilter: const {})); @override Future close() async { await _sub.cancel(); return super.close(); } }