Files
sendico/frontend/pshared/lib/provider/template.dart
2025-12-05 01:32:41 +01:00

231 lines
6.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<T> mergeLists<T>({
required List<T> lhs,
required List<T> rhs,
required Comparable Function(T) getKey, // Extracts ID dynamically
required int Function(T, T) compare,
required T Function(T, T) merge,
}) {
final result = <T>[];
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 reimplementing service logic.
class GenericProvider<T extends PermissionBoundStorable> extends ChangeNotifier {
final BasicService<T> service;
bool _isLoaded = false;
Resource<List<T>> _resource = Resource(data: []);
Resource<List<T>> get resource => _resource;
List<T> get items => List.unmodifiable(_resource.data ?? []);
bool get isLoading => _resource.isLoading;
Object? get error => _resource.error;
bool get isReady => (error == null) && _isLoaded;
bool get isCurrentSet => _currentObjectRef != null;
String? _currentObjectRef; // Stores the currently selected project ref
T? get currentObject => _resource.data?.firstWhereOrNull(
(object) => object.id == _currentObjectRef,
);
T? getItemByRef(String id) => items.firstWhereOrNull((item) => item.id == id);
GenericProvider({required this.service});
bool Function(T)? _filterPredicate;
List<T> 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<List<T>> newResource) {
_resource = newResource;
notifyListeners();
}
Future<List<T>> loadFuture(Future<List<T>> future) async {
_setResource(_resource.copyWith(isLoading: true));
try {
final list = await future;
_isLoaded = true;
_setResource(Resource(data: list, isLoading: false));
return list;
} catch (e) {
_setResource(
_resource.copyWith(isLoading: false, error: toException(e)),
);
rethrow;
}
}
Future<void> load(
String organizationRef,
String? parentRef, {
int? limit,
int? offset,
bool? Function()? fetchArchived,
}) async {
if (parentRef != null) {
await loadFuture(
service.list(
organizationRef,
parentRef,
limit: limit,
offset: offset,
fetchArchived: fetchArchived == null ? null : fetchArchived(),
),
);
}
}
Future<void> loadItem(String itemRef) async {
await loadFuture((() async => [await service.get(itemRef)])());
}
List<T> merge(List<T> rhs) => mergeLists<T>(
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<T> 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<T> createObject(String organizationRef, Map<String, dynamic> 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<void> update(Map<String, dynamic> 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<void> delete(String objectRef, {Map<String, dynamic>? request}) async {
_setResource(_resource.copyWith(isLoading: true));
try {
await service.delete(objectRef, request: request);
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;
}
}
Future<void> toggleArchived(T item, bool currentState, {bool? cascade}) => setArchived(
organizationRef: item.organizationRef,
objectRef: item.id,
newIsArchived: !currentState,
cascade: cascade ?? true,
);
Future<void> setArchived({
required String organizationRef,
required String objectRef,
required bool newIsArchived,
bool? cascade,
}) async {
_setResource(_resource.copyWith(isLoading: true));
try {
await service.archive(
organizationRef: organizationRef,
objectRef: objectRef,
newIsArchived: newIsArchived,
cascade: cascade,
);
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 (_currentObjectRef == objectRef) {
// No change, skip notification
return true;
}
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
}
}