Remove DiffUtil and MoorAnimatedList

This commit is contained in:
Simon Binder 2019-09-13 21:06:11 +02:00
parent c8ae99b52e
commit 138652fdc4
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
5 changed files with 0 additions and 490 deletions

View File

@ -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;
}

View File

@ -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');
}

View File

@ -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);
});
}

View File

@ -12,7 +12,6 @@ import 'package:moor/moor.dart';
import 'package:moor/backends.dart'; import 'package:moor/backends.dart';
import 'package:sqflite/sqflite.dart' as s; import 'package:sqflite/sqflite.dart' as s;
export 'package:moor_flutter/src/animated_list.dart';
export 'package:moor/moor.dart'; export 'package:moor/moor.dart';
/// Signature of a function that runs when a database doesn't exist on file. /// Signature of a function that runs when a database doesn't exist on file.

View 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;
},
);
}
}