Files
sendico/frontend/pshared/lib/provider/template.dart
Stephan D c6a56071b5
Some checks failed
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
+signup +login
2025-11-17 20:16:45 +01:00

171 lines
5.0 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;
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;
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<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<void> loadFuture(Future<List<T>> 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<void> load(String organizationRef, String? parentRef) async {
if (parentRef != null) {
return loadFuture(service.list(organizationRef, parentRef));
}
}
Future<void> loadItem(String itemRef) async {
return 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) 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
}
}