Add rejected mutation requeue flow in sync UI

This commit is contained in:
Rijad Zuzo
2026-02-15 23:06:48 +01:00
parent 831c217e29
commit c4b86c18da
5 changed files with 205 additions and 7 deletions
+57 -3
View File
@@ -68,22 +68,39 @@ class SyncQueueRepository extends AsyncNotifier<SyncState> {
Future<void> applyPushResult(SyncPushResult result, {DateTime? at}) async {
final SyncState current = await _currentState();
final DateTime now = at ?? DateTime.now();
final Set<String> completedMutationIds = <String>{
...result.accepted.map((MutationAck ack) => ack.clientMutationId),
final Set<String> rejectedMutationIds = <String>{
...result.rejected.map(
(MutationRejection rejection) => rejection.clientMutationId,
),
};
final Set<String> completedMutationIds = <String>{
...result.accepted.map((MutationAck ack) => ack.clientMutationId),
...rejectedMutationIds,
};
final List<ChangeEnvelope> rejectedChanges = current.pendingChanges
.where(
(ChangeEnvelope change) =>
rejectedMutationIds.contains(change.clientMutationId),
)
.toList(growable: false);
final List<ChangeEnvelope> pending = current.pendingChanges
.where(
(ChangeEnvelope change) =>
!completedMutationIds.contains(change.clientMutationId),
)
.toList(growable: false);
final List<ChangeEnvelope> dedupedRejectedChanges = rejectedChanges
.where(
(ChangeEnvelope change) => !pending.any(
(ChangeEnvelope pendingChange) =>
pendingChange.clientMutationId == change.clientMutationId,
),
)
.toList(growable: false);
final String? rejectionMessage = result.rejected.isEmpty
? null
: 'Push rejected ${result.rejected.length} change(s).';
: 'Push rejected ${result.rejected.length} change(s). Fix locally and retry.';
await _setState(
current.copyWith(
@@ -93,6 +110,29 @@ class SyncQueueRepository extends AsyncNotifier<SyncState> {
lastSyncAt: now,
lastError: rejectionMessage,
lastRejected: result.rejected,
lastRejectedChanges: dedupedRejectedChanges,
),
);
}
/// Requeues rejected changes to pending sync list for manual retry.
Future<void> requeueRejectedChanges() async {
final SyncState current = await _currentState();
if (current.lastRejectedChanges.isEmpty) {
return;
}
final List<ChangeEnvelope> pending = <ChangeEnvelope>[
...current.lastRejectedChanges,
...current.pendingChanges,
];
final List<ChangeEnvelope> dedupedPending = _dedupeByMutationId(pending);
await _setState(
current.copyWith(
pendingChanges: dedupedPending,
lastRejected: const <MutationRejection>[],
lastRejectedChanges: const <ChangeEnvelope>[],
lastError: null,
),
);
}
@@ -107,6 +147,7 @@ class SyncQueueRepository extends AsyncNotifier<SyncState> {
lastSyncAt: now,
lastError: null,
lastRejected: const <MutationRejection>[],
lastRejectedChanges: const <ChangeEnvelope>[],
),
);
}
@@ -123,6 +164,7 @@ class SyncQueueRepository extends AsyncNotifier<SyncState> {
current.copyWith(
pendingChanges: const <ChangeEnvelope>[],
lastRejected: const <MutationRejection>[],
lastRejectedChanges: const <ChangeEnvelope>[],
),
);
}
@@ -138,11 +180,23 @@ class SyncQueueRepository extends AsyncNotifier<SyncState> {
await _setState(
current.copyWith(
lastRejected: const <MutationRejection>[],
lastRejectedChanges: const <ChangeEnvelope>[],
lastError: lastError,
),
);
}
List<ChangeEnvelope> _dedupeByMutationId(List<ChangeEnvelope> changes) {
final Set<String> seen = <String>{};
final List<ChangeEnvelope> deduped = <ChangeEnvelope>[];
for (final ChangeEnvelope change in changes) {
if (seen.add(change.clientMutationId)) {
deduped.add(change);
}
}
return deduped;
}
Future<SyncState> _currentState() async {
final SyncState? value = state.asData?.value;
if (value != null) {