feat: Implement note encryption and synchronization features
- Added NoteEncryption class for encrypting and decrypting note content using AES-GCM. - Updated NoteRepository to handle synchronization of notes and categories with the server, including encryption of note data before sending. - Introduced SyncRequest and SyncResponse models for managing synchronization data. - Enhanced LocalVaultService to store and retrieve the encryption key. - Modified HomeScreen and SettingsScreen to trigger synchronization after note operations and manage API endpoint settings. - Added SyncStatusIndicator to provide visual feedback on synchronization status in the app title bar. - Created Category model to manage note categories with encryption support. - Updated note model to include UUID, server version, deletion status, and category ID. - Added necessary UI elements for displaying and managing the encryption key in SettingsScreen. - Updated dependencies in pubspec.yaml for cryptography and HTTP handling.
This commit is contained in:
+62
-19
@@ -7,39 +7,70 @@ import 'package:path_provider/path_provider.dart';
|
||||
|
||||
part 'app_database.g.dart';
|
||||
|
||||
@DataClassName('DbCategory')
|
||||
class Categories extends Table {
|
||||
TextColumn get uuid => text().unique()();
|
||||
TextColumn get encryptedName => text().named('encrypted_name')();
|
||||
IntColumn get serverVersion => integer().named('server_version').withDefault(const Constant(0))();
|
||||
BoolColumn get isDeleted => boolean().named('is_deleted').withDefault(const Constant(false))();
|
||||
DateTimeColumn get updatedAt => dateTime().named('updated_at')();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {uuid};
|
||||
}
|
||||
|
||||
@DataClassName('DbNote')
|
||||
class Notes extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get uuid => text().unique()();
|
||||
TextColumn get title => text()();
|
||||
TextColumn get body => text()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
DateTimeColumn get createdAt => dateTime().named('created_at')();
|
||||
DateTimeColumn get updatedAt => dateTime().named('updated_at')();
|
||||
IntColumn get sortIndex => integer().named('sort_index')();
|
||||
IntColumn get serverVersion => integer().named('server_version').withDefault(const Constant(0))();
|
||||
BoolColumn get isDeleted => boolean().named('is_deleted').withDefault(const Constant(false))();
|
||||
TextColumn get categoryId => text().nullable().named('category_id')();
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [Notes])
|
||||
@DriftDatabase(tables: [Notes, Categories])
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase({required String encryptionKey}) : super(_openConnection(encryptionKey));
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
AppDatabase({required String encryptionKey}) : super(_openConnection(encryptionKey));
|
||||
|
||||
// ========== Categories ==========
|
||||
Future<List<DbCategory>> getAllCategories() {
|
||||
return (select(categories)..where((c) => c.isDeleted.equals(false))).get();
|
||||
}
|
||||
|
||||
Future<int> upsertCategory(CategoriesCompanion category) {
|
||||
return into(categories).insertOnConflictUpdate(category);
|
||||
}
|
||||
|
||||
Future<void> deleteCategory(String uuid) {
|
||||
return (update(categories)..where((c) => c.uuid.equals(uuid)))
|
||||
.write(CategoriesCompanion(isDeleted: Value(true)));
|
||||
}
|
||||
|
||||
// ========== Notes ==========
|
||||
Future<List<DbNote>> getAllNotes() {
|
||||
return (select(notes)..orderBy([
|
||||
(note) => OrderingTerm(expression: note.sortIndex),
|
||||
])).get();
|
||||
return (select(notes)
|
||||
..orderBy([(note) => OrderingTerm(expression: note.sortIndex)])
|
||||
..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');
|
||||
await customStatement('UPDATE notes SET sort_index = sort_index + 1 WHERE is_deleted = 0');
|
||||
return into(notes).insert(note.copyWith(sortIndex: const Value<int>(0)));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> replaceAllNotes(List<NotesCompanion> noteList) {
|
||||
return transaction(() async {
|
||||
await delete(notes).go();
|
||||
await (delete(notes)..where((n) => n.isDeleted.equals(false))).go();
|
||||
|
||||
for (final NotesCompanion note in noteList) {
|
||||
await into(notes).insert(note);
|
||||
@@ -51,17 +82,20 @@ class AppDatabase extends _$AppDatabase {
|
||||
return update(notes).replace(note);
|
||||
}
|
||||
|
||||
Future<void> deleteNote(int id, int removedIndex) async {
|
||||
await (update(notes)..where((n) => n.id.equals(id))).write(NotesCompanion(isDeleted: Value(true)));
|
||||
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ? AND is_deleted = 0',
|
||||
[removedIndex],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteNoteAndShift({
|
||||
required int id,
|
||||
required int removedIndex,
|
||||
}) {
|
||||
return transaction(() async {
|
||||
await (delete(notes)..where((note) => note.id.equals(id))).go();
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ?',
|
||||
[removedIndex],
|
||||
);
|
||||
});
|
||||
return deleteNote(id, removedIndex);
|
||||
}
|
||||
|
||||
Future<void> moveNote({
|
||||
@@ -76,12 +110,12 @@ class AppDatabase extends _$AppDatabase {
|
||||
return transaction(() async {
|
||||
if (oldIndex < newIndex) {
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ? AND sort_index <= ?',
|
||||
'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ? AND sort_index <= ? AND is_deleted = 0',
|
||||
[oldIndex, newIndex],
|
||||
);
|
||||
} else {
|
||||
await customStatement(
|
||||
'UPDATE notes SET sort_index = sort_index + 1 WHERE sort_index >= ? AND sort_index < ?',
|
||||
'UPDATE notes SET sort_index = sort_index + 1 WHERE sort_index >= ? AND sort_index < ? AND is_deleted = 0',
|
||||
[newIndex, oldIndex],
|
||||
);
|
||||
}
|
||||
@@ -92,6 +126,15 @@ class AppDatabase extends _$AppDatabase {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ========== Sync helpers ==========
|
||||
Future<List<DbNote>> getUnsyncedNotes() {
|
||||
return (select(notes)..where((n) => n.isDeleted.equals(true) | n.serverVersion.equals(0))).get();
|
||||
}
|
||||
|
||||
Future<List<DbCategory>> getUnsyncedCategories() {
|
||||
return (select(categories)..where((c) => c.isDeleted.equals(true) | c.serverVersion.equals(0))).get();
|
||||
}
|
||||
}
|
||||
|
||||
LazyDatabase _openConnection(String encryptionKey) {
|
||||
|
||||
Reference in New Issue
Block a user