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: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.
|
||||||
|
|
|
@ -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