Merge branch 'feat/issue-7-undo-log'
This commit is contained in:
@@ -91,3 +91,5 @@ snap/
|
||||
*.log
|
||||
runner-data/
|
||||
sharedinbox-runner/runner-data/
|
||||
.gemini/
|
||||
.rustup/
|
||||
|
||||
@@ -21,11 +21,21 @@ class UndoService extends StateNotifier<List<UndoAction>> {
|
||||
state = [];
|
||||
}
|
||||
|
||||
Future<void> undo() async {
|
||||
Future<void> undo({String? actionId}) async {
|
||||
if (state.isEmpty) return;
|
||||
|
||||
final action = state.last;
|
||||
state = state.sublist(0, state.length - 1);
|
||||
final UndoAction action;
|
||||
if (actionId == null) {
|
||||
action = state.last;
|
||||
state = state.sublist(0, state.length - 1);
|
||||
} else {
|
||||
try {
|
||||
action = state.firstWhere((a) => a.id == actionId);
|
||||
state = state.where((a) => a.id != actionId).toList();
|
||||
} catch (e) {
|
||||
return; // Action not found
|
||||
}
|
||||
}
|
||||
|
||||
final repo = _ref.read(emailRepositoryProvider);
|
||||
|
||||
|
||||
@@ -56,22 +56,13 @@ class _UndoActionTile extends ConsumerWidget {
|
||||
subtitle: Text(_timeFmt.format(action.timestamp.toLocal())),
|
||||
trailing: TextButton(
|
||||
onPressed: () async {
|
||||
// In a real log, "undoing" an old action might be complex
|
||||
// if subsequent actions conflict. For now, we only support
|
||||
// undoing the LATEST action via the global UndoService.
|
||||
// To keep it simple, we check if this is the latest action.
|
||||
final history = ref.read(undoServiceProvider);
|
||||
final latest = history.isNotEmpty ? history.last : null;
|
||||
if (latest?.id == action.id) {
|
||||
await ref.read(undoServiceProvider.notifier).undo();
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Only the latest action can be undone.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
await ref
|
||||
.read(undoServiceProvider.notifier)
|
||||
.undo(actionId: action.id);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Action undone.')),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Undo'),
|
||||
|
||||
@@ -88,6 +88,36 @@ void main() {
|
||||
verify(mockEmailRepo.moveEmail('e1', 'INBOX')).called(1);
|
||||
});
|
||||
|
||||
test('undo with actionId removes and undos specific action', () async {
|
||||
final action1 = UndoAction(
|
||||
id: '1',
|
||||
accountId: 'acc1',
|
||||
type: UndoType.move,
|
||||
emailIds: ['e1'],
|
||||
sourceMailboxPath: 'INBOX',
|
||||
);
|
||||
final action2 = UndoAction(
|
||||
id: '2',
|
||||
accountId: 'acc1',
|
||||
type: UndoType.delete,
|
||||
emailIds: ['e2'],
|
||||
sourceMailboxPath: 'INBOX',
|
||||
);
|
||||
|
||||
when(mockEmailRepo.moveEmail(any, any)).thenAnswer((_) async {});
|
||||
when(mockEmailRepo.cancelPendingChange(any, any))
|
||||
.thenAnswer((_) async => false);
|
||||
|
||||
final notifier = container.read(undoServiceProvider.notifier);
|
||||
notifier.pushAction(action1);
|
||||
notifier.pushAction(action2);
|
||||
|
||||
await notifier.undo(actionId: '1');
|
||||
expect(container.read(undoServiceProvider), [action2]);
|
||||
verify(mockEmailRepo.moveEmail('e1', 'INBOX')).called(1);
|
||||
verifyNever(mockEmailRepo.moveEmail('e2', 'INBOX'));
|
||||
});
|
||||
|
||||
test('undo performs cancelPendingChange optimization', () async {
|
||||
final action = UndoAction(
|
||||
id: '1',
|
||||
|
||||
Reference in New Issue
Block a user