Merge branch 'feat/issue-7-undo-log'
This commit is contained in:
@@ -91,3 +91,5 @@ snap/
|
|||||||
*.log
|
*.log
|
||||||
runner-data/
|
runner-data/
|
||||||
sharedinbox-runner/runner-data/
|
sharedinbox-runner/runner-data/
|
||||||
|
.gemini/
|
||||||
|
.rustup/
|
||||||
|
|||||||
@@ -21,11 +21,21 @@ class UndoService extends StateNotifier<List<UndoAction>> {
|
|||||||
state = [];
|
state = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> undo() async {
|
Future<void> undo({String? actionId}) async {
|
||||||
if (state.isEmpty) return;
|
if (state.isEmpty) return;
|
||||||
|
|
||||||
final action = state.last;
|
final UndoAction action;
|
||||||
state = state.sublist(0, state.length - 1);
|
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);
|
final repo = _ref.read(emailRepositoryProvider);
|
||||||
|
|
||||||
|
|||||||
@@ -56,22 +56,13 @@ class _UndoActionTile extends ConsumerWidget {
|
|||||||
subtitle: Text(_timeFmt.format(action.timestamp.toLocal())),
|
subtitle: Text(_timeFmt.format(action.timestamp.toLocal())),
|
||||||
trailing: TextButton(
|
trailing: TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// In a real log, "undoing" an old action might be complex
|
await ref
|
||||||
// if subsequent actions conflict. For now, we only support
|
.read(undoServiceProvider.notifier)
|
||||||
// undoing the LATEST action via the global UndoService.
|
.undo(actionId: action.id);
|
||||||
// To keep it simple, we check if this is the latest action.
|
if (context.mounted) {
|
||||||
final history = ref.read(undoServiceProvider);
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
final latest = history.isNotEmpty ? history.last : null;
|
const SnackBar(content: Text('Action undone.')),
|
||||||
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.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('Undo'),
|
child: const Text('Undo'),
|
||||||
|
|||||||
@@ -88,6 +88,36 @@ void main() {
|
|||||||
verify(mockEmailRepo.moveEmail('e1', 'INBOX')).called(1);
|
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 {
|
test('undo performs cancelPendingChange optimization', () async {
|
||||||
final action = UndoAction(
|
final action = UndoAction(
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|||||||
Reference in New Issue
Block a user