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
171 lines
5.0 KiB
Dart
171 lines
5.0 KiB
Dart
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 re‑implementing 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
|
||
}
|
||
}
|