import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:pshared/models/permissions/bound/storable.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/template.dart'; import 'package:pshared/utils/exception.dart'; List mergeLists({ required List lhs, required List rhs, required Comparable Function(T) getKey, // Extracts ID dynamically required int Function(T, T) compare, required T Function(T, T) merge, }) { final result = []; final map = {for (var item in lhs) getKey(item): item}; for (var updated in rhs) { final key = getKey(updated); map[key] = merge(map[key] ?? updated, updated); } result.addAll(map.values); result.sort(compare); return result; } /// A generic provider that wraps a [BasicService] instance /// to manage state (loading, error, data) without re‑implementing service logic. class GenericProvider extends ChangeNotifier { final BasicService service; Resource> _resource = Resource(data: []); Resource> get resource => _resource; List get items => List.unmodifiable(_resource.data ?? []); bool get isLoading => _resource.isLoading; bool get isEmpty => items.isEmpty; Object? get error => _resource.error; String? _currentObjectRef; // Stores the currently selected project ref T? get currentObject => _resource.data?.firstWhereOrNull( (object) => object.id == _currentObjectRef, ); T? getItemById(String id) => items.firstWhereOrNull((item) => item.id == id); GenericProvider({required this.service}); bool Function(T)? _filterPredicate; List get filteredItems => _filterPredicate != null ? items.where(_filterPredicate!).toList() : items; void setFilterPredicate(bool Function(T)? predicate) { _filterPredicate = predicate; notifyListeners(); } void clearFilter() => setFilterPredicate(null); void _setResource(Resource> newResource) { _resource = newResource; notifyListeners(); } Future loadFuture(Future> future) async { _setResource(_resource.copyWith(isLoading: true)); try { final list = await future; _setResource(Resource(data: list, isLoading: false)); } catch (e) { _setResource( _resource.copyWith(isLoading: false, error: toException(e)), ); rethrow; } } Future load(String organizationRef, String? parentRef) async { if (parentRef != null) { return loadFuture(service.list(organizationRef, parentRef)); } } Future loadItem(String itemRef) async { return loadFuture((() async => [await service.get(itemRef)])()); } List merge(List rhs) => mergeLists( lhs: items, rhs: rhs, getKey: (item) => item.id, // Key extractor compare: (a, b) => a.id.compareTo(b.id), // Sorting logic merge: (existing, updated) => updated, // Replace with the updated version ); Future get(String objectRef) async { _setResource(_resource.copyWith(isLoading: true)); try { final item = await service.get(objectRef); _setResource(Resource(data: merge([item]), isLoading: false)); return item; } catch (e) { _setResource(_resource.copyWith(isLoading: false, error: toException(e))); rethrow; } } Future createObject(String organizationRef, Map request) async { _setResource(_resource.copyWith(isLoading: true)); try { final newObject = await service.create(organizationRef, request); _setResource(Resource(data: [...items, ...newObject], isLoading: false)); return newObject.first; } catch (e) { _setResource(_resource.copyWith(isLoading: false, error: toException(e))); rethrow; } } Future update(Map request) async { _setResource(_resource.copyWith(isLoading: true)); try { final list = await service.update(request); _setResource(Resource(data: merge(list), isLoading: false)); } catch (e) { _setResource(_resource.copyWith(isLoading: false, error: toException(e))); rethrow; } } Future delete(String objectRef) async { _setResource(_resource.copyWith(isLoading: true)); try { await service.delete(objectRef); if (_currentObjectRef == objectRef) { _currentObjectRef = null; } _setResource(Resource( data: _resource.data?.where((p) => p.id != objectRef).toList(), isLoading: false, )); } catch (e) { _setResource(Resource(data: _resource.data, isLoading: false, error: toException(e))); rethrow; } } bool setCurrentObject(String? objectRef) { if (objectRef == null) { _currentObjectRef = null; notifyListeners(); return true; } if (_resource.data?.any((p) => p.id == objectRef) ?? false) { _currentObjectRef = objectRef; notifyListeners(); return true; } return false; // Object not found } }