Skip to content

Instantly share code, notes, and snippets.

@lvsecoto
Last active May 29, 2024 07:43
Show Gist options
  • Save lvsecoto/fe07363d0e2c9baf8d5a36e65ff4a07e to your computer and use it in GitHub Desktop.
Save lvsecoto/fe07363d0e2c9baf8d5a36e65ff4a07e to your computer and use it in GitHub Desktop.
/// 对象状态[T]的历史堆栈,可以推入或者弹出
class HistoryStack<T> {
/// 最大索引的数据,即为最新的状态
final _store = <T>[];
/// 读取内容,用于保存的数据
({
List<T> store,
int step,
}) read() {
return (
store: _store.toList(),
step: _step,
);
}
final List<void Function()> _listeners = [];
/// 添加监听器
void addListener(void Function() onChange) {
_listeners.add(onChange);
}
/// 删除监听器
void removeListener(void Function() onChange) {
_listeners.remove(onChange);
}
/// 恢复状态
void restore({
required List<T> store,
required int step,
}) {
_store.clear();
_store.addAll(store);
_step = step;
}
/// 在哪一条历史记录
///
/// 为历史数目时,为历史堆栈的最新状态,越小,代表历史堆栈的最早状态
int _step = 0;
/// 压入当前的状态
///
/// **[step]之后的记录会全部抹除**
void record(T state) {
_store.removeRange(_step, _store.length);
_store.add(state);
_step = _store.length;
_notifyListeners();
}
/// 是否可以撤销操作
bool get canUndo {
return _step > 1;
}
/// 撤销
T undo() {
if (canUndo) {
_step--;
_notifyListeners();
}
return current;
}
/// 是否可以重做操作
bool get canRedo {
return _step < _store.length;
}
/// 重做
T redo() {
if (canRedo) {
_step++;
_notifyListeners();
}
return current;
}
/// 清除历史堆栈
void clear() {
if (_store.isEmpty) {
return;
}
_store.clear();
_step = 0;
}
/// 当前状态
T get current => _store.elementAt(_step - 1);
/// 通知监听器
void _notifyListeners() {
for (final it in _listeners) {
it.call();
}
}
}
import 'package:flutter_sudoku/common/history/history.dart';
import 'package:test/test.dart';
void main() {
late HistoryStack<String> history;
test('测试撤销重做逻辑', () {
history = HistoryStack<String>();
history.record('hello');
history.record('world');
expect(history.undo(), 'hello');
expect(history.redo(), 'world');
expect(history.undo(), 'hello');
expect(history.redo(), 'world');
expect(history.undo(), 'hello');
});
test('测试在撤销后,再压入新的状态,之前撤销的记录会消失', () {
history = HistoryStack<String>();
history.record('hello');
history.record('world');
history.record('now');
expect(history.undo(), 'world');
expect(history.undo(), 'hello');
history.record('again');
expect(history.undo(), 'hello');
expect(history.redo(), 'again');
});
test('测试没有历史时,一直撤销的话,只会保持返回最早的状态', () {
history = HistoryStack<String>();
history.record('hello');
history.record('world');
_repeat(10, () {
expect(history.undo(), 'hello');
expect(history.undo(), 'hello');
});
});
test('测试当前是最新的状态的话,一直重做,只会保持返回最晚的状态', () {
history = HistoryStack<String>();
history.record('hello');
history.record('world');
history.undo();
_repeat(10, () {
expect(history.redo(), 'world');
expect(history.redo(), 'world');
});
});
test('测试是否撤销重做可用状态检测', () {
history = HistoryStack<String>();
history.record('hello');
history.record('world');
history.record('again');
expect(history.canUndo, true);
expect(history.canRedo, false);
expect(history.undo(), 'world');
expect(history.canUndo, true);
expect(history.canRedo, true);
expect(history.undo(), 'hello');
expect(history.canUndo, false);
expect(history.canRedo, true);
expect(history.redo(), 'world');
expect(history.canUndo, true);
expect(history.canRedo, true);
expect(history.redo(), 'again');
expect(history.canUndo, true);
expect(history.canRedo, false);
});
test('测试状态监听回调,同步历史记录相关状态', () {
history = HistoryStack<String>();
var canUndo = false;
var canRedo = false;
history.addListener(() {
canUndo = history.canUndo;
canRedo = history.canRedo;
});
expect(canUndo, false);
expect(canRedo, false);
history.record('hello');
// 只有一个记录时,是不可以撤销的
expect(canUndo, false);
expect(canRedo, false);
history.record('world');
expect(canUndo, true);
expect(canRedo, false);
history.undo();
expect(canUndo, false);
expect(canRedo, true);
history.redo();
expect(canUndo, true);
expect(canRedo, false);
});
test('测试清除历史', () {
history = HistoryStack<String>();
history.record('hello');
history.record('hello world');
expect(history.canUndo, true);
history.clear();
expect(history.canUndo, false);
expect(history.canRedo, false);
});
}
void _repeat(int time, void Function() block) {
assert(time > 0);
for (int i = 0; i < time; i++) {
block();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment