feat: files in an order of one's own, with Reorder beside the tabs
A PDF now has a position among its studyset's files, as notes and paths have: a fractional index in a column of its own (so moving it on one device and renaming it on another don't overwrite each other), last writer wins. The migration adds it to pdf_source on the server and on each device with 'a0', so PDFs from before stand in the order they were added (ties sort by id), and new ones go at the end. The Files tab lists them in that order by default (Custom order, first in the Sort menu; newest first was the default before). With two files or more, Reorder beside the tabs shows the custom order whatever they were sorted by, a grip in place of each file's options and the Sort menu put away, until Done. The next release changes synced tables anyway (card.buriedAt, since v0.2.2), so older apps have to update for that already. Also: card.position's doc no longer speaks of moving cards. 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:
parent
04cbf00e3c
commit
7975586994
3 changed files with 21780 additions and 44 deletions
|
|
@ -102,15 +102,15 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
// The newest first.
|
||||
// In the person's own order by default: as added, until reordered.
|
||||
expect(
|
||||
[for (final row in state.rows) row.source.content.title],
|
||||
[
|
||||
'Kana',
|
||||
'Grammar',
|
||||
'Kana',
|
||||
],
|
||||
);
|
||||
final row = state.rows.last;
|
||||
final row = state.rows.first;
|
||||
expect(row.cards, 1);
|
||||
expect(row.notes, 1);
|
||||
expect(row.furthestPage, 7);
|
||||
|
|
@ -138,6 +138,29 @@ void main() {
|
|||
bloc.add(const PdfListEventSorted(PdfSort.largest));
|
||||
await until((s) => s.sort == PdfSort.largest);
|
||||
expect(titles().first, 'a');
|
||||
bloc.add(const PdfListEventSorted(PdfSort.newest));
|
||||
await until((s) => s.sort == PdfSort.newest);
|
||||
expect(titles(), ['c', 'a', 'b']);
|
||||
});
|
||||
|
||||
test("moves a PDF in the person's own order, which the other orders "
|
||||
'leave be', () async {
|
||||
await add('b', 'b');
|
||||
await add('a', 'a');
|
||||
final c = await add('c', 'c');
|
||||
start();
|
||||
await until((s) => s.rows.length == 3);
|
||||
List<String> titles() => [
|
||||
for (final row in bloc.state.rows) row.source.content.title,
|
||||
];
|
||||
|
||||
bloc.add(PdfListEventMoveRequested(c, 0));
|
||||
await until((s) => s.rows.first.source.id == c);
|
||||
expect(titles(), ['c', 'b', 'a']);
|
||||
|
||||
bloc.add(const PdfListEventSorted(PdfSort.oldest));
|
||||
await until((s) => s.sort == PdfSort.oldest);
|
||||
expect(titles(), ['b', 'a', 'c']);
|
||||
});
|
||||
|
||||
test('leaves out a PDF whose deletion can still be undone', () async {
|
||||
|
|
|
|||
|
|
@ -131,6 +131,41 @@ void main() {
|
|||
expect((await pdfs.byId(id))!.content.deadline, isNull);
|
||||
});
|
||||
|
||||
test('lists PDFs in their order: as added, PDFs from before there was one '
|
||||
'too, until moved', () async {
|
||||
// From before positions: all at a0, in the order they were added.
|
||||
gateway
|
||||
..receiveRemote(
|
||||
id: 'pdf-0901',
|
||||
studysetId: 'japanese',
|
||||
payload: encodePdfSourcePayload(
|
||||
const PdfSourceContent(title: 'old 1', fileName: '', assetId: ''),
|
||||
),
|
||||
)
|
||||
..receiveRemote(
|
||||
id: 'pdf-0902',
|
||||
studysetId: 'japanese',
|
||||
payload: encodePdfSourcePayload(
|
||||
const PdfSourceContent(title: 'old 2', fileName: '', assetId: ''),
|
||||
),
|
||||
);
|
||||
final added = await pdfs.import(
|
||||
'japanese',
|
||||
bytes: pdf('new'),
|
||||
fileName: 'new.pdf',
|
||||
);
|
||||
Future<List<String>> titles() async => [
|
||||
for (final source in await pdfs.sourcesOf('japanese'))
|
||||
source.content.title,
|
||||
];
|
||||
expect(await titles(), ['old 1', 'old 2', 'new']);
|
||||
|
||||
await pdfs.move('japanese', (added as PdfImported).id, toIndex: 0);
|
||||
expect(await titles(), ['new', 'old 1', 'old 2']);
|
||||
await pdfs.move('japanese', 'pdf-0901', toIndex: 2);
|
||||
expect(await titles(), ['new', 'old 2', 'old 1']);
|
||||
});
|
||||
|
||||
test("leaves out a PDF it can't read, and counts the others by "
|
||||
'studyset', () async {
|
||||
await pdfs.import('japanese', bytes: pdf('a'), fileName: 'a.pdf');
|
||||
|
|
|
|||
|
|
@ -176,7 +176,9 @@ void main() {
|
|||
}
|
||||
await showApp(tester);
|
||||
double top(String title) => tester.getTopLeft(find.text(title)).dy;
|
||||
expect(top('a'), lessThan(top('c')));
|
||||
// In the person's own order by default: as added, until reordered.
|
||||
expect(top('b'), lessThan(top('c')));
|
||||
expect(top('c'), lessThan(top('a')));
|
||||
|
||||
await tester.tap(find.byTooltip('Sort'));
|
||||
await tester.pumpAndSettle();
|
||||
|
|
@ -186,6 +188,52 @@ void main() {
|
|||
expect(top('b'), lessThan(top('c')));
|
||||
});
|
||||
|
||||
testWidgets('Reorder beside the tabs shows the custom order, whatever the '
|
||||
'files were sorted by, and drags a file elsewhere in it, until '
|
||||
'Done', (tester) async {
|
||||
final pdfs = repository();
|
||||
for (final name in ['b', 'a']) {
|
||||
await pdfs.import('japanese', bytes: pdf(name), fileName: '$name.pdf');
|
||||
}
|
||||
await showApp(tester);
|
||||
await tester.tap(find.byTooltip('Sort'));
|
||||
await tester.pumpAndSettle();
|
||||
await choose(tester, 'Name (A–Z)');
|
||||
double top(String title) => tester.getTopLeft(find.text(title)).dy;
|
||||
expect(top('a'), lessThan(top('b')));
|
||||
|
||||
await tester.tap(find.text('Reorder'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byTooltip('Drag to reorder'), findsNWidgets(2));
|
||||
expect(find.byTooltip('Sort'), findsNothing);
|
||||
expect(top('b'), lessThan(top('a')));
|
||||
|
||||
// The first file, dragged below the second.
|
||||
final row = tester.getSize(find.widgetWithText(ListTile, 'b'));
|
||||
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, row.height * 1.5 / 6));
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
}
|
||||
await drag.up();
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
[
|
||||
for (final source in await pdfs.sourcesOf('japanese'))
|
||||
source.content.title,
|
||||
],
|
||||
['a', 'b'],
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Done'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byTooltip('Drag to reorder'), findsNothing);
|
||||
expect(find.byTooltip('Sort'), findsOneWidget);
|
||||
expect(top('a'), lessThan(top('b')));
|
||||
});
|
||||
|
||||
testWidgets('renames, and deletes with a moment to undo', (tester) async {
|
||||
await repository().import(
|
||||
'japanese',
|
||||
|
|
|
|||
Loading…
Reference in a new issue