mirror of https://github.com/AMT-Cheif/drift.git
Remove DiffUtil and MoorAnimatedList
This commit is contained in:
parent
c8ae99b52e
commit
138652fdc4
|
@ -1,81 +0,0 @@
|
|||
/// A utility library to find an edit script that turns a list into another.
|
||||
/// This is useful when displaying a updating stream of immutable lists in a
|
||||
/// list that can be updated.
|
||||
@Deprecated('Will be removed in moor 2.0')
|
||||
library diff_util;
|
||||
|
||||
import 'package:moor/src/utils/android_diffutils_port.dart' as impl;
|
||||
|
||||
class EditAction {
|
||||
/// The index of the first list on which this action should be applied. If
|
||||
/// this action [isDelete], that index and the next [amount] indices should be
|
||||
/// deleted. Otherwise, this index should be moved back by [amount] and
|
||||
/// entries from the second list (starting at [indexFromOther]) should be
|
||||
/// inserted into the gap.
|
||||
final int index;
|
||||
|
||||
/// The amount of entries affected by this action
|
||||
final int amount;
|
||||
|
||||
/// If this action [isInsert], this is the first index from the second list
|
||||
/// from where the items should be taken from.
|
||||
final int indexFromOther;
|
||||
|
||||
/// Whether this action should delete entries from the first list
|
||||
bool get isDelete => indexFromOther == null;
|
||||
|
||||
/// Whether this action should insert entries into the first list
|
||||
bool get isInsert => indexFromOther != null;
|
||||
|
||||
EditAction(this.index, this.amount, this.indexFromOther);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (isDelete) {
|
||||
return 'EditAction: Delete $amount entries from the first list, starting '
|
||||
'at index $index';
|
||||
} else {
|
||||
return 'EditAction: Insert $amount entries into the first list, taking '
|
||||
'them from the second list starting at $indexFromOther. The entries '
|
||||
'should be written starting at index $index';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the shortest edit script that turns list [a] into list [b].
|
||||
/// The implementation is ported from androids DiffUtil, which in turn
|
||||
/// implements a variation of Eugene W. Myer's difference algorithm. The
|
||||
/// algorithm is optimized for space and uses O(n) space to find the minimal
|
||||
/// number of addition and removal operations between the two lists. It has
|
||||
/// O(N + D^2) time performance, where D is the minimum amount of edits needed
|
||||
/// to turn a into b.
|
||||
List<EditAction> diff<T>(List<T> a, List<T> b,
|
||||
{bool Function(T a, T b) equals}) {
|
||||
final defaultEquals = equals ?? (T a, T b) => a == b;
|
||||
final snakes = impl.calculateDiff(impl.DiffInput<T>(a, b, defaultEquals));
|
||||
final actions = <EditAction>[];
|
||||
|
||||
var posOld = a.length;
|
||||
var posNew = b.length;
|
||||
for (var snake in snakes.reversed) {
|
||||
final snakeSize = snake.size;
|
||||
final endX = snake.x + snakeSize;
|
||||
final endY = snake.y + snakeSize;
|
||||
|
||||
if (endX < posOld) {
|
||||
// starting (including) with index endX, delete posOld - endX chars from a
|
||||
actions.add(EditAction(endX, posOld - endX, null));
|
||||
}
|
||||
if (endY < posNew) {
|
||||
// starting with index endX, insert posNex - endY characters into a. The
|
||||
// characters should be taken from b, starting (including) at the index
|
||||
// endY. The char that was at index endX should be pushed back.
|
||||
actions.add(EditAction(endX, posNew - endY, endY));
|
||||
}
|
||||
|
||||
posOld = snake.x;
|
||||
posNew = snake.y;
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
|
@ -1,249 +0,0 @@
|
|||
// ignore_for_file: cascade_invocations
|
||||
|
||||
/*
|
||||
This implementation is copied from the DiffUtil class of the android support
|
||||
library, available at https://chromium.googlesource.com/android_tools/+/refs/heads/master/sdk/sources/android-25/android/support/v7/util/DiffUtil.java
|
||||
It has the following license:
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class Snake {
|
||||
int x;
|
||||
int y;
|
||||
int size;
|
||||
bool removal;
|
||||
bool reverse;
|
||||
}
|
||||
|
||||
class Range {
|
||||
int oldListStart, oldListEnd;
|
||||
int newListStart, newListEnd;
|
||||
|
||||
Range.nullFields();
|
||||
Range(this.oldListStart, this.oldListEnd, this.newListStart, this.newListEnd);
|
||||
}
|
||||
|
||||
class DiffInput<T> {
|
||||
final List<T> from;
|
||||
final List<T> to;
|
||||
final bool Function(T a, T b) equals;
|
||||
|
||||
DiffInput(this.from, this.to, this.equals);
|
||||
|
||||
bool areItemsTheSame(int fromPos, int toPos) {
|
||||
return equals(from[fromPos], to[toPos]);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in moor 2.0')
|
||||
List<Snake> calculateDiff(DiffInput input) {
|
||||
final oldSize = input.from.length;
|
||||
final newSize = input.to.length;
|
||||
|
||||
final snakes = <Snake>[];
|
||||
final stack = <Range>[];
|
||||
|
||||
stack.add(Range(0, oldSize, 0, newSize));
|
||||
|
||||
final max = oldSize + newSize + (oldSize - newSize).abs();
|
||||
|
||||
final forward = List<int>(max * 2);
|
||||
final backward = List<int>(max * 2);
|
||||
|
||||
final rangePool = <Range>[];
|
||||
|
||||
while (stack.isNotEmpty) {
|
||||
final range = stack.removeLast();
|
||||
final snake = _diffPartial(input, range.oldListStart, range.oldListEnd,
|
||||
range.newListStart, range.newListEnd, forward, backward, max);
|
||||
|
||||
if (snake != null) {
|
||||
if (snake.size > 0) {
|
||||
snakes.add(snake);
|
||||
}
|
||||
|
||||
// offset the snake to convert its coordinates from the Range's are to
|
||||
// global
|
||||
snake.x += range.oldListStart;
|
||||
snake.y += range.newListStart;
|
||||
|
||||
// add new ranges for left and right
|
||||
final left =
|
||||
rangePool.isEmpty ? Range.nullFields() : rangePool.removeLast();
|
||||
left.oldListStart = range.oldListStart;
|
||||
left.newListStart = range.newListStart;
|
||||
if (snake.reverse) {
|
||||
left.oldListEnd = snake.x;
|
||||
left.newListEnd = snake.y;
|
||||
} else {
|
||||
if (snake.removal) {
|
||||
left.oldListEnd = snake.x - 1;
|
||||
left.newListEnd = snake.y;
|
||||
} else {
|
||||
left.oldListEnd = snake.x;
|
||||
left.newListEnd = snake.y - 1;
|
||||
}
|
||||
}
|
||||
stack.add(left);
|
||||
|
||||
final right = range;
|
||||
if (snake.reverse) {
|
||||
if (snake.removal) {
|
||||
right.oldListStart = snake.x + snake.size + 1;
|
||||
right.newListStart = snake.y + snake.size;
|
||||
} else {
|
||||
right.oldListStart = snake.x + snake.size;
|
||||
right.newListStart = snake.y + snake.size + 1;
|
||||
}
|
||||
} else {
|
||||
right.oldListStart = snake.x + snake.size;
|
||||
right.newListStart = snake.y + snake.size;
|
||||
}
|
||||
stack.add(right);
|
||||
} else {
|
||||
rangePool.add(range);
|
||||
}
|
||||
}
|
||||
|
||||
snakes.sort((a, b) {
|
||||
final cmpX = a.x - b.x;
|
||||
return cmpX == 0 ? a.y - b.y : cmpX;
|
||||
});
|
||||
|
||||
// add root snake
|
||||
final first = snakes.isEmpty ? null : snakes.first;
|
||||
|
||||
if (first == null || first.x != 0 || first.y != 0) {
|
||||
snakes.insert(
|
||||
0,
|
||||
Snake()
|
||||
..x = 0
|
||||
..y = 0
|
||||
..removal = false
|
||||
..size = 0
|
||||
..reverse = false);
|
||||
}
|
||||
|
||||
return snakes;
|
||||
}
|
||||
|
||||
Snake _diffPartial(DiffInput input, int startOld, int endOld, int startNew,
|
||||
int endNew, List<int> forward, List<int> backward, int kOffset) {
|
||||
final oldSize = endOld - startOld;
|
||||
final newSize = endNew - startNew;
|
||||
|
||||
if (endOld - startOld < 1 || endNew - startNew < 1) return null;
|
||||
|
||||
final delta = oldSize - newSize;
|
||||
final dLimit = (oldSize + newSize + 1) ~/ 2;
|
||||
|
||||
forward.fillRange(kOffset - dLimit - 1, kOffset + dLimit + 1, 0);
|
||||
backward.fillRange(
|
||||
kOffset - dLimit - 1 + delta, kOffset + dLimit + 1 + delta, oldSize);
|
||||
|
||||
final checkInFwd = delta.isOdd;
|
||||
|
||||
for (var d = 0; d <= dLimit; d++) {
|
||||
for (var k = -d; k <= d; k += 2) {
|
||||
// find forward path
|
||||
// we can reach k from k - 1 or k + 1. Check which one is further in the
|
||||
// graph.
|
||||
int x;
|
||||
bool removal;
|
||||
|
||||
if (k == -d ||
|
||||
k != d && forward[kOffset + k - 1] < forward[kOffset + k + 1]) {
|
||||
x = forward[kOffset + k + 1];
|
||||
removal = false;
|
||||
} else {
|
||||
x = forward[kOffset + k - 1] + 1;
|
||||
removal = true;
|
||||
}
|
||||
|
||||
// set y based on x
|
||||
var y = x - k;
|
||||
|
||||
// move diagonal as long as items match
|
||||
while (x < oldSize &&
|
||||
y < newSize &&
|
||||
input.areItemsTheSame(startOld + x, startNew + y)) {
|
||||
x++;
|
||||
y++;
|
||||
}
|
||||
|
||||
forward[kOffset + k] = x;
|
||||
|
||||
if (checkInFwd && k >= delta - d + 1 && k <= delta + d - 1) {
|
||||
if (forward[kOffset + k] >= backward[kOffset + k]) {
|
||||
final outSnake = Snake()..x = backward[kOffset + k];
|
||||
outSnake
|
||||
..y = outSnake.x - k
|
||||
..size = forward[kOffset + k] - backward[kOffset + k]
|
||||
..removal = removal
|
||||
..reverse = false;
|
||||
|
||||
return outSnake;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var k = -d; k <= d; k += 2) {
|
||||
// find reverse path at k + delta, in reverse
|
||||
final backwardK = k + delta;
|
||||
int x;
|
||||
bool removal;
|
||||
|
||||
if (backwardK == d + delta ||
|
||||
backwardK != -d + delta &&
|
||||
backward[kOffset + backwardK - 1] <
|
||||
backward[kOffset + backwardK + 1]) {
|
||||
x = backward[kOffset + backwardK - 1];
|
||||
removal = false;
|
||||
} else {
|
||||
x = backward[kOffset + backwardK + 1] - 1;
|
||||
removal = true;
|
||||
}
|
||||
|
||||
// set y based on x
|
||||
var y = x - backwardK;
|
||||
// move diagonal as long as items match
|
||||
while (x > 0 &&
|
||||
y > 0 &&
|
||||
input.areItemsTheSame(startOld + x - 1, startNew + y - 1)) {
|
||||
x--;
|
||||
y--;
|
||||
}
|
||||
|
||||
backward[kOffset + backwardK] = x;
|
||||
|
||||
if (!checkInFwd && k + delta >= -d && k + delta <= d) {
|
||||
if (forward[kOffset + backwardK] >= backward[kOffset + backwardK]) {
|
||||
final outSnake = Snake()..x = backward[kOffset + backwardK];
|
||||
outSnake
|
||||
..y = outSnake.x - backwardK
|
||||
..size =
|
||||
forward[kOffset + backwardK] - backward[kOffset + backwardK]
|
||||
..removal = removal
|
||||
..reverse = true;
|
||||
|
||||
return outSnake;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw StateError("Unexpected case: Please make sure the lists don't change "
|
||||
'during a diff');
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import 'package:test_api/test_api.dart';
|
||||
import 'package:moor/diff_util.dart';
|
||||
|
||||
List<T> applyEditScript<T>(List<T> a, List<T> b, List<EditAction> actions) {
|
||||
final copy = List.of(a);
|
||||
|
||||
for (var action in actions) {
|
||||
if (action.isDelete) {
|
||||
final deleteStartIndex = action.index;
|
||||
copy.removeRange(deleteStartIndex, deleteStartIndex + action.amount);
|
||||
} else if (action.isInsert) {
|
||||
final toAdd = b.getRange(
|
||||
action.indexFromOther, action.indexFromOther + action.amount);
|
||||
copy.insertAll(action.index, toAdd);
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
void main() {
|
||||
final a = ['a', 'b', 'c', 'a', 'b', 'b', 'a'];
|
||||
final b = ['c', 'b', 'a', 'b', 'a', 'c'];
|
||||
|
||||
test('diff matcher should produce a correct edit script', () {
|
||||
expect(applyEditScript(a, b, diff(a, b)), b);
|
||||
});
|
||||
}
|
|
@ -12,7 +12,6 @@ import 'package:moor/moor.dart';
|
|||
import 'package:moor/backends.dart';
|
||||
import 'package:sqflite/sqflite.dart' as s;
|
||||
|
||||
export 'package:moor_flutter/src/animated_list.dart';
|
||||
export 'package:moor/moor.dart';
|
||||
|
||||
/// Signature of a function that runs when a database doesn't exist on file.
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
import 'package:moor/diff_util.dart';
|
||||
|
||||
typedef Widget ItemBuilder<T>(
|
||||
BuildContext context, T item, Animation<double> anim);
|
||||
typedef Widget RemovedItemBuilder<T>(
|
||||
BuildContext context, T item, Animation<double> anim);
|
||||
|
||||
/// An [AnimatedList] that shows the result of a moor query stream.
|
||||
@Deprecated('Will be removed in moor 2.0. You could use the '
|
||||
'animated_stream_list package as an alternative')
|
||||
class MoorAnimatedList<T> extends StatefulWidget {
|
||||
final Stream<List<T>> stream;
|
||||
final ItemBuilder<T> itemBuilder;
|
||||
final RemovedItemBuilder<T> removedItemBuilder;
|
||||
|
||||
/// A function that decides whether two items are considered equal. By
|
||||
/// default, `a == b` will be used. A customization is useful if the content
|
||||
/// of items can change (e.g. when a title changes, you'd only want to change
|
||||
/// one text and not let the item disappear to show up again).
|
||||
final bool Function(T a, T b) equals;
|
||||
|
||||
MoorAnimatedList(
|
||||
{@required this.stream,
|
||||
@required this.itemBuilder,
|
||||
@required this.removedItemBuilder,
|
||||
this.equals});
|
||||
|
||||
@override
|
||||
_MoorAnimatedListState<T> createState() {
|
||||
return _MoorAnimatedListState<T>();
|
||||
}
|
||||
}
|
||||
|
||||
class _MoorAnimatedListState<T> extends State<MoorAnimatedList<T>> {
|
||||
List<T> _lastSnapshot;
|
||||
int _initialItemCount;
|
||||
|
||||
StreamSubscription _subscription;
|
||||
|
||||
final GlobalKey<AnimatedListState> _key = GlobalKey();
|
||||
AnimatedListState get listState => _key.currentState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_setupSubscription();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _receiveData(List<T> data) {
|
||||
if (listState == null) {
|
||||
setState(() {
|
||||
_lastSnapshot = data;
|
||||
_initialItemCount = data.length;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastSnapshot == null) {
|
||||
// no diff possible. Initialize lists instead of diffing
|
||||
_lastSnapshot = data;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
listState.insertItem(i);
|
||||
}
|
||||
} else {
|
||||
final editScript = diff(_lastSnapshot, data, equals: widget.equals);
|
||||
|
||||
for (var action in editScript) {
|
||||
if (action.isDelete) {
|
||||
// we need to delete action.amount items at index action.index
|
||||
for (var i = 0; i < action.amount; i++) {
|
||||
// i items have already been deleted, so + 1 for the index. Notice
|
||||
// that we don't have to do this when calling removeItem on the
|
||||
// animated list state, as it will reflect the operation immediately.
|
||||
final itemHere = _lastSnapshot[action.index + i];
|
||||
listState.removeItem(action.index, (ctx, anim) {
|
||||
return widget.removedItemBuilder(ctx, itemHere, anim);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < action.amount; i++) {
|
||||
listState.insertItem(action.index + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_lastSnapshot = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _setupSubscription() {
|
||||
_subscription = widget.stream.listen(_receiveData);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MoorAnimatedList<T> oldWidget) {
|
||||
_subscription?.cancel();
|
||||
_lastSnapshot = null;
|
||||
_setupSubscription();
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_lastSnapshot == null) return const SizedBox();
|
||||
|
||||
return AnimatedList(
|
||||
key: _key,
|
||||
initialItemCount: _initialItemCount ?? 0,
|
||||
itemBuilder: (ctx, index, anim) {
|
||||
final item = _lastSnapshot[index];
|
||||
final child = widget.itemBuilder(ctx, item, anim);
|
||||
|
||||
return child;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue