refactor(app): flashcards keep the order they came in, without Reorder

Studying breaks the order of due cards anyway; what follows the list
order is new cards and cramming, and that order comes from creating and
importing, with sub-studysets putting lessons in order. No one drags a
card among a thousand (decided 2026-10-08).

Gone: Reorder and the grip on flashcards, CardListEventMoved,
CardRepository.move and the gateway's updatePosition, with their tests.
Each card keeps its stored position, so the order stays the same on
every device. liftWhileDragged is Material's plain lift again, for the
lists that keep Reorder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-08 18:42:15 +02:00
commit 6c9dfadf27
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 97 additions and 410 deletions

View file

@ -95,30 +95,6 @@ void main() {
),
);
});
test('moves a card', () async {
await cards.create('japanese', card('a', ''));
await cards.create('japanese', card('b', ''));
final bloc = CardListBloc(cards, tags, deletions, 'japanese')
..add(const CardListEventStarted());
addTearDown(bloc.close);
final loaded = await bloc.stream.firstWhere(
(state) => state is CardListStateLoaded,
) as CardListStateLoaded;
bloc.add(CardListEventMoved(loaded.cards.last, toIndex: 0));
await expectLater(
bloc.stream,
emits(
isA<CardListStateLoaded>().having(
(s) => [for (final card in s.cards) card.content.front],
'fronts',
[text('b'), text('a')],
),
),
);
});
});
group('CardListBloc with tags', () {

View file

@ -195,25 +195,6 @@ void main() {
expect(await repository.headsOf('card-9999'), isEmpty);
});
test(
'moves a card between two others, to the start and to the end',
() async {
for (final front in ['a', 'b', 'c']) {
await repository.create('japanese', card(front, ''));
}
Future<List<Card>> cards() => repository.cardsOf('japanese');
await repository.move('japanese', (await cards())[2], toIndex: 1);
expect(fronts(await cards()), ['a', 'c', 'b']);
await repository.move('japanese', (await cards())[2], toIndex: 0);
expect(fronts(await cards()), ['b', 'a', 'c']);
await repository.move('japanese', (await cards())[0], toIndex: 2);
expect(fronts(await cards()), ['a', 'c', 'b']);
},
);
test('deleting blanks every revision before the card goes', () async {
final id = await repository.create('japanese', card('秘密', 'secret'));
await repository.save(

View file

@ -239,7 +239,7 @@ void main() {
testWidgets(
"on desktop, a card's whole … opens its options, with no handle to "
'drag it over it',
'drag it over it: cards keep the order they came in',
(tester) async {
await cards.create('japanese', card('猫', 'cat'));
await cards.create('japanese', card('犬', 'dog'));
@ -247,6 +247,7 @@ void main() {
expect(find.byIcon(Icons.drag_handle), findsNothing);
expect(find.byTooltip('Drag to reorder'), findsNothing);
expect(find.text('Reorder'), findsNothing);
await tester.tap(optionsOf('猫'));
await tester.pumpAndSettle();
expect(find.text('Edit'), findsOneWidget);
@ -254,54 +255,6 @@ void main() {
variant: TargetPlatformVariant.only(TargetPlatform.linux),
);
testWidgets('Reorder beside the tabs gives each card a handle to drag it '
'by instead of its options, until Done', (tester) async {
final first = await cards.create('japanese', card('猫', 'cat'));
final second = await cards.create('japanese', card('犬', 'dog'));
await showList(tester);
await tester.tap(find.text('Reorder'));
await tester.pumpAndSettle();
expect(find.byTooltip('Drag to reorder'), findsNWidgets(2));
expect(optionsOf('猫'), findsNothing);
// The first card, dragged below the second.
final rowHeight = tester.getSize(find.widgetWithText(CardTile, '猫')).height;
final drag = await tester.startGesture(
tester.getCenter(find.byTooltip('Drag to reorder').first),
);
for (var step = 0; step < 6; step++) {
await drag.moveBy(Offset(0, rowHeight * 1.5 / 6));
await tester.pump(const Duration(milliseconds: 50));
}
await drag.up();
await tester.pumpAndSettle();
expect(
[
for (final card in await cards.cardsOf('japanese')) card.id,
],
[second, first],
);
expect(
tester.getTopLeft(find.text('犬')).dy,
lessThan(tester.getTopLeft(find.text('猫')).dy),
);
await tester.tap(find.text('Done'));
await tester.pumpAndSettle();
expect(find.byTooltip('Drag to reorder'), findsNothing);
expect(optionsOf('猫'), findsOneWidget);
});
testWidgets('Reorder shows with two cards or more, all of them showing', (
tester,
) async {
await cards.create('japanese', card('猫', 'cat'));
await showList(tester);
expect(find.text('Reorder'), findsNothing);
});
testWidgets('builds only the cards in view, so a studyset of a thousand '
'opens as fast as one of ten', (tester) async {
await cards.createAll('japanese', [

View file

@ -129,18 +129,6 @@ class FakeCardGateway() implements CardGateway {
return revision.id;
}
@override
Future<void> updatePosition(String cardId, String position) async {
if (_cards[cardId] case final card?) {
_cards[cardId] = (
studysetId: card.studysetId,
position: position,
buriedAt: card.buriedAt,
createdAt: card.createdAt,
);
}
}
@override
Future<void> updateBuriedAt(String cardId, DateTime? buriedAt) async {
if (_cards[cardId] case final card?) {

View file

@ -131,30 +131,6 @@ void main() {
expect(resolved.content.back, text('a cat'));
});
test('a card buried on one device and moved on another at once is both '
'on each', () async {
final phone = await device('phone');
final laptop = await device('laptop');
final (studyset, id) = await shared(phone, laptop);
await phone.cards.create(studyset, card('犬', 'dog'));
await syncBoth(phone, laptop);
final at = DateTime.utc(2026, 10, 7, 9);
await phone.cards.bury(id, at);
await laptop.cards.move(
studyset,
(await laptop.card(studyset, id))!,
toIndex: 1,
);
await syncBoth(phone, laptop);
for (final device in [phone, laptop]) {
final moved = (await device.cards.cardsOf(studyset)).last;
expect(moved.id, id);
expect(moved.buriedAt, at);
}
});
test('ratings on two devices both count, and both devices compute the '
'same schedule', () async {
final phone = await device('phone');