feat: Implement note positioning logic and tests for position conversion
This commit is contained in:
+106
-39
@@ -5,6 +5,8 @@ import 'package:drift/native.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:notas/data/note_positioning.dart';
|
||||
|
||||
part 'app_database.g.dart';
|
||||
|
||||
@DataClassName('DbCategory')
|
||||
@@ -48,7 +50,7 @@ class Notes extends Table {
|
||||
@DriftDatabase(tables: [Notes, Categories])
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
@override
|
||||
int get schemaVersion => 3;
|
||||
int get schemaVersion => 4;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -69,6 +71,29 @@ class AppDatabase extends _$AppDatabase {
|
||||
await customStatement('UPDATE categories SET color_value = NULL');
|
||||
await customStatement('UPDATE categories SET icon_code_point = NULL');
|
||||
}
|
||||
if (from < 4) {
|
||||
final List<DbNote> activeNotes = await (select(notes)
|
||||
..where((n) => n.isDeleted.equals(false))
|
||||
..orderBy([
|
||||
(note) => OrderingTerm(expression: note.sortIndex),
|
||||
]))
|
||||
.get();
|
||||
|
||||
final List<int> rebalancedPositions = rebalanceNotePositions(
|
||||
activeNotes.length,
|
||||
);
|
||||
|
||||
for (var index = 0; index < activeNotes.length; index += 1) {
|
||||
final DbNote row = activeNotes[index];
|
||||
await (update(notes)..where((n) => n.id.equals(row.id))).write(
|
||||
NotesCompanion(
|
||||
sortIndex: Value(rebalancedPositions[index]),
|
||||
updatedAt: Value(DateTime.now()),
|
||||
isDirty: const Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -97,17 +122,37 @@ class AppDatabase extends _$AppDatabase {
|
||||
// ========== Notes ==========
|
||||
Future<List<DbNote>> getAllNotes() {
|
||||
return (select(notes)
|
||||
..orderBy([(note) => OrderingTerm(expression: note.sortIndex)])
|
||||
..orderBy([
|
||||
(note) => OrderingTerm(
|
||||
expression: note.sortIndex,
|
||||
mode: OrderingMode.desc,
|
||||
),
|
||||
])
|
||||
..where((n) => n.isDeleted.equals(false)))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<int> insertNoteAtTop(NotesCompanion note) {
|
||||
return transaction(() async {
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index + 1, is_dirty = 1 WHERE is_deleted = 0',
|
||||
final DbNote? topNote = await (select(notes)
|
||||
..where((n) => n.isDeleted.equals(false))
|
||||
..orderBy([
|
||||
(note) => OrderingTerm(
|
||||
expression: note.sortIndex,
|
||||
mode: OrderingMode.desc,
|
||||
),
|
||||
])
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
|
||||
final int nextSortIndex = topNote == null
|
||||
? 0
|
||||
: topNote.sortIndex + notePositionStep;
|
||||
|
||||
await into(notes).insert(
|
||||
note.copyWith(sortIndex: Value<int>(nextSortIndex)),
|
||||
);
|
||||
return into(notes).insert(note.copyWith(sortIndex: const Value<int>(0)));
|
||||
return nextSortIndex;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -133,11 +178,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
isDirty: const Value(true),
|
||||
),
|
||||
);
|
||||
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index - 1, is_dirty = 1 WHERE sort_index > ? AND is_deleted = 0',
|
||||
[removedIndex],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteNoteAndShift({
|
||||
@@ -165,44 +205,64 @@ class AppDatabase extends _$AppDatabase {
|
||||
required int oldIndex,
|
||||
required int newIndex,
|
||||
}) {
|
||||
if (oldIndex == newIndex) {
|
||||
return Future<void>.value();
|
||||
}
|
||||
|
||||
return transaction(() async {
|
||||
final List<DbNote> all = await (select(
|
||||
notes,
|
||||
)..where((n) => n.isDeleted.equals(false))).get();
|
||||
final List<DbNote> orderedNotes = await (select(notes)
|
||||
..where((n) => n.isDeleted.equals(false))
|
||||
..orderBy([
|
||||
(note) => OrderingTerm(
|
||||
expression: note.sortIndex,
|
||||
mode: OrderingMode.desc,
|
||||
),
|
||||
]))
|
||||
.get();
|
||||
|
||||
final int count = all.length;
|
||||
if (count == 0) {
|
||||
final int currentIndex = orderedNotes.indexWhere((DbNote row) => row.id == id);
|
||||
if (currentIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int maxIndex = count - 1;
|
||||
|
||||
final int safeOld = oldIndex.clamp(0, maxIndex);
|
||||
final int safeNew = newIndex.clamp(0, maxIndex);
|
||||
|
||||
if (safeOld == safeNew) {
|
||||
final int safeNewIndex = newIndex.clamp(0, orderedNotes.length - 1);
|
||||
if (currentIndex == safeNewIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (safeOld < safeNew) {
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index - 1, is_dirty = 1 WHERE sort_index > ? AND sort_index <= ? AND is_deleted = 0',
|
||||
[safeOld, safeNew],
|
||||
);
|
||||
final DbNote movedNote = orderedNotes.removeAt(currentIndex);
|
||||
orderedNotes.insert(safeNewIndex, movedNote);
|
||||
|
||||
final int? newStoredPosition;
|
||||
if (safeNewIndex == 0) {
|
||||
newStoredPosition = orderedNotes[1].sortIndex + notePositionStep;
|
||||
} else if (safeNewIndex == orderedNotes.length - 1) {
|
||||
newStoredPosition =
|
||||
orderedNotes[orderedNotes.length - 2].sortIndex - notePositionStep;
|
||||
} else {
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index + 1, is_dirty = 1 WHERE sort_index >= ? AND sort_index < ? AND is_deleted = 0',
|
||||
[safeNew, safeOld],
|
||||
newStoredPosition = midpointNotePosition(
|
||||
higherPosition: orderedNotes[safeNewIndex - 1].sortIndex,
|
||||
lowerPosition: orderedNotes[safeNewIndex + 1].sortIndex,
|
||||
);
|
||||
}
|
||||
|
||||
if (newStoredPosition == null) {
|
||||
final List<int> rebalancedPositions = rebalanceNotePositions(
|
||||
orderedNotes.length,
|
||||
);
|
||||
|
||||
for (var index = 0; index < orderedNotes.length; index += 1) {
|
||||
final DbNote row = orderedNotes[index];
|
||||
await (update(notes)..where((n) => n.id.equals(row.id))).write(
|
||||
NotesCompanion(
|
||||
sortIndex: Value<int>(rebalancedPositions[index]),
|
||||
updatedAt: Value<DateTime>(DateTime.now()),
|
||||
isDirty: const Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await (update(notes)..where((n) => n.id.equals(id))).write(
|
||||
NotesCompanion(
|
||||
sortIndex: Value<int>(safeNew),
|
||||
sortIndex: Value<int>(newStoredPosition),
|
||||
updatedAt: Value<DateTime>(DateTime.now()),
|
||||
isDirty: const Value(true),
|
||||
),
|
||||
@@ -221,11 +281,18 @@ class AppDatabase extends _$AppDatabase {
|
||||
// and at least one of `title` or `body` is not empty. Previously the
|
||||
// query required both title AND body to be non-empty which excluded
|
||||
// notes that had an empty body (common) from appearing in the trash.
|
||||
return (select(notes)..where(
|
||||
(n) => n.isDeleted.equals(true) &
|
||||
(n.title.isNotValue('') | n.body.isNotValue('')),
|
||||
))
|
||||
.get();
|
||||
return (select(notes)
|
||||
..where(
|
||||
(n) => n.isDeleted.equals(true) &
|
||||
(n.title.isNotValue('') | n.body.isNotValue('')),
|
||||
)
|
||||
..orderBy([
|
||||
(note) => OrderingTerm(
|
||||
expression: note.sortIndex,
|
||||
mode: OrderingMode.desc,
|
||||
),
|
||||
]))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<List<DbCategory>> getCategoriesChangedSince(DateTime since) {
|
||||
|
||||
Reference in New Issue
Block a user