refactor: Update Note and Category models to use 'id' instead of 'uuid', and adjust related database operations
- Changed 'uuid' to 'id' in Note and Category models for consistency. - Updated database operations in NoteRepository to reflect the new 'id' field. - Modified sync models to accommodate changes in Note and Category structures. - Adjusted the handling of notes and categories during synchronization. - Refactored the note editor and home screen to use the new 'id' field. - Ensured that the 'isDirty' flag is properly set and utilized across models.
This commit is contained in:
+2
-2
@@ -753,14 +753,14 @@ class _NotesAppState extends State<NotesApp>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_syncStatus = SyncStatus.error;
|
_syncStatus = SyncStatus.error;
|
||||||
_syncErrorMessage = e.toString();
|
_syncErrorMessage = '$e\n\nStackTrace: $st';
|
||||||
_syncProgress = null;
|
_syncProgress = null;
|
||||||
_syncDetailMessage = null;
|
_syncDetailMessage = null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -277,9 +277,23 @@ class AuthApi {
|
|||||||
debugPrint('Response body: ${res.body}');
|
debugPrint('Response body: ${res.body}');
|
||||||
|
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
final Map<String, dynamic> json =
|
try {
|
||||||
jsonDecode(res.body) as Map<String, dynamic>;
|
final Map<String, dynamic> json =
|
||||||
return {'error': false, 'data': SyncResponse.fromJson(json)};
|
jsonDecode(res.body) as Map<String, dynamic>;
|
||||||
|
return {'error': false, 'data': SyncResponse.fromJson(json)};
|
||||||
|
} catch (e, st) {
|
||||||
|
debugPrint('SYNC PARSE ERROR -> $e');
|
||||||
|
debugPrint(st.toString());
|
||||||
|
debugPrint('SYNC PARSE RAW BODY -> ${res.body}');
|
||||||
|
return {
|
||||||
|
'error': true,
|
||||||
|
'message': 'Error parseando respuesta de sync: $e',
|
||||||
|
'exception': e.toString(),
|
||||||
|
'stackTrace': st.toString(),
|
||||||
|
'body': res.body,
|
||||||
|
'status': res.statusCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If token expired (401), try to refresh
|
// If token expired (401), try to refresh
|
||||||
@@ -298,8 +312,16 @@ class AuthApi {
|
|||||||
try {
|
try {
|
||||||
final dynamic decoded = jsonDecode(res.body);
|
final dynamic decoded = jsonDecode(res.body);
|
||||||
return {'error': true, 'status': res.statusCode, 'body': decoded};
|
return {'error': true, 'status': res.statusCode, 'body': decoded};
|
||||||
} catch (_) {
|
} catch (e, st) {
|
||||||
return {'error': true, 'status': res.statusCode, 'body': res.body};
|
debugPrint('SYNC HTTP ERROR PARSE FAILED -> $e');
|
||||||
|
debugPrint(st.toString());
|
||||||
|
return {
|
||||||
|
'error': true,
|
||||||
|
'status': res.statusCode,
|
||||||
|
'body': res.body,
|
||||||
|
'exception': e.toString(),
|
||||||
|
'stackTrace': st.toString(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+52
-26
@@ -9,22 +9,23 @@ part 'app_database.g.dart';
|
|||||||
|
|
||||||
@DataClassName('DbCategory')
|
@DataClassName('DbCategory')
|
||||||
class Categories extends Table {
|
class Categories extends Table {
|
||||||
TextColumn get uuid => text().unique()();
|
TextColumn get id => text()();
|
||||||
TextColumn get encryptedName => text().named('encrypted_name')();
|
TextColumn get encryptedName => text().named('encrypted_name')();
|
||||||
IntColumn get serverVersion =>
|
IntColumn get serverVersion =>
|
||||||
integer().named('server_version').withDefault(const Constant(0))();
|
integer().named('server_version').withDefault(const Constant(0))();
|
||||||
BoolColumn get isDeleted =>
|
BoolColumn get isDeleted =>
|
||||||
boolean().named('is_deleted').withDefault(const Constant(false))();
|
boolean().named('is_deleted').withDefault(const Constant(false))();
|
||||||
|
BoolColumn get isDirty =>
|
||||||
|
boolean().named('is_dirty').withDefault(const Constant(true))();
|
||||||
DateTimeColumn get updatedAt => dateTime().named('updated_at')();
|
DateTimeColumn get updatedAt => dateTime().named('updated_at')();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {uuid};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataClassName('DbNote')
|
@DataClassName('DbNote')
|
||||||
class Notes extends Table {
|
class Notes extends Table {
|
||||||
IntColumn get id => integer().autoIncrement()();
|
TextColumn get id => text().named('id')();
|
||||||
TextColumn get uuid => text().unique()();
|
|
||||||
TextColumn get title => text()();
|
TextColumn get title => text()();
|
||||||
TextColumn get body => text()();
|
TextColumn get body => text()();
|
||||||
DateTimeColumn get createdAt => dateTime().named('created_at')();
|
DateTimeColumn get createdAt => dateTime().named('created_at')();
|
||||||
@@ -35,12 +36,34 @@ class Notes extends Table {
|
|||||||
BoolColumn get isDeleted =>
|
BoolColumn get isDeleted =>
|
||||||
boolean().named('is_deleted').withDefault(const Constant(false))();
|
boolean().named('is_deleted').withDefault(const Constant(false))();
|
||||||
TextColumn get categoryId => text().nullable().named('category_id')();
|
TextColumn get categoryId => text().nullable().named('category_id')();
|
||||||
|
BoolColumn get isDirty =>
|
||||||
|
boolean().named('is_dirty').withDefault(const Constant(true))();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
|
|
||||||
@DriftDatabase(tables: [Notes, Categories])
|
@DriftDatabase(tables: [Notes, Categories])
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 1;
|
int get schemaVersion => 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
|
onCreate: (Migrator migrator) async {
|
||||||
|
await migrator.createAll();
|
||||||
|
},
|
||||||
|
onUpgrade: (Migrator migrator, int from, int to) async {
|
||||||
|
if (from < 2) {
|
||||||
|
await migrator.addColumn(notes, notes.isDirty);
|
||||||
|
await migrator.addColumn(categories, categories.isDirty);
|
||||||
|
|
||||||
|
await customStatement('UPDATE notes SET is_dirty = 0');
|
||||||
|
await customStatement('UPDATE categories SET is_dirty = 0');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
AppDatabase({required String encryptionKey})
|
AppDatabase({required String encryptionKey})
|
||||||
: super(_openConnection(encryptionKey));
|
: super(_openConnection(encryptionKey));
|
||||||
|
|
||||||
@@ -53,8 +76,8 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
return into(categories).insertOnConflictUpdate(category);
|
return into(categories).insertOnConflictUpdate(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteCategory(String uuid) {
|
Future<void> deleteCategory(String id) {
|
||||||
return (update(categories)..where((c) => c.uuid.equals(uuid))).write(
|
return (update(categories)..where((c) => c.id.equals(id))).write(
|
||||||
CategoriesCompanion(isDeleted: Value(true)),
|
CategoriesCompanion(isDeleted: Value(true)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -70,7 +93,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
Future<int> insertNoteAtTop(NotesCompanion note) {
|
Future<int> insertNoteAtTop(NotesCompanion note) {
|
||||||
return transaction(() async {
|
return transaction(() async {
|
||||||
await customStatement(
|
await customStatement(
|
||||||
'UPDATE notes SET sort_index = sort_index + 1 WHERE is_deleted = 0',
|
'UPDATE notes SET sort_index = sort_index + 1, is_dirty = 1 WHERE is_deleted = 0',
|
||||||
);
|
);
|
||||||
return into(notes).insert(note.copyWith(sortIndex: const Value<int>(0)));
|
return into(notes).insert(note.copyWith(sortIndex: const Value<int>(0)));
|
||||||
});
|
});
|
||||||
@@ -90,28 +113,29 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
return update(notes).replace(note);
|
return update(notes).replace(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteNote(int id, int removedIndex) async {
|
Future<void> deleteNote(String id, int removedIndex) async {
|
||||||
await (update(notes)..where((n) => n.id.equals(id))).write(
|
await (update(notes)..where((n) => n.id.equals(id))).write(
|
||||||
NotesCompanion(
|
NotesCompanion(
|
||||||
isDeleted: const Value(true),
|
isDeleted: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(DateTime.now()),
|
||||||
|
isDirty: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await customStatement(
|
await customStatement(
|
||||||
'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ? AND is_deleted = 0',
|
'UPDATE notes SET sort_index = sort_index - 1, is_dirty = 1 WHERE sort_index > ? AND is_deleted = 0',
|
||||||
[removedIndex],
|
[removedIndex],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteNoteAndShift({
|
Future<void> deleteNoteAndShift({
|
||||||
required int id,
|
required String id,
|
||||||
required int removedIndex,
|
required int removedIndex,
|
||||||
}) {
|
}) {
|
||||||
return deleteNote(id, removedIndex);
|
return deleteNote(id, removedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> permanentlyDeleteNote(int id) async {
|
Future<void> permanentlyDeleteNote(String id) async {
|
||||||
await (update(notes)..where((n) => n.id.equals(id))).write(
|
await (update(notes)..where((n) => n.id.equals(id))).write(
|
||||||
NotesCompanion(
|
NotesCompanion(
|
||||||
title: const Value(''),
|
title: const Value(''),
|
||||||
@@ -119,12 +143,13 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
categoryId: const Value(null),
|
categoryId: const Value(null),
|
||||||
isDeleted: const Value(true),
|
isDeleted: const Value(true),
|
||||||
updatedAt: Value(DateTime.now()),
|
updatedAt: Value(DateTime.now()),
|
||||||
|
isDirty: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> moveNote({
|
Future<void> moveNote({
|
||||||
required int id,
|
required String id,
|
||||||
required int oldIndex,
|
required int oldIndex,
|
||||||
required int newIndex,
|
required int newIndex,
|
||||||
}) {
|
}) {
|
||||||
@@ -133,9 +158,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return transaction(() async {
|
return transaction(() async {
|
||||||
final List<DbNote> all = await (select(notes)
|
final List<DbNote> all = await (select(
|
||||||
..where((n) => n.isDeleted.equals(false)))
|
notes,
|
||||||
.get();
|
)..where((n) => n.isDeleted.equals(false))).get();
|
||||||
|
|
||||||
final int count = all.length;
|
final int count = all.length;
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
@@ -153,12 +178,12 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
|
|
||||||
if (safeOld < safeNew) {
|
if (safeOld < safeNew) {
|
||||||
await customStatement(
|
await customStatement(
|
||||||
'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ? AND sort_index <= ? AND is_deleted = 0',
|
'UPDATE notes SET sort_index = sort_index - 1, is_dirty = 1 WHERE sort_index > ? AND sort_index <= ? AND is_deleted = 0',
|
||||||
[safeOld, safeNew],
|
[safeOld, safeNew],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await customStatement(
|
await customStatement(
|
||||||
'UPDATE notes SET sort_index = sort_index + 1 WHERE sort_index >= ? AND sort_index < ? AND is_deleted = 0',
|
'UPDATE notes SET sort_index = sort_index + 1, is_dirty = 1 WHERE sort_index >= ? AND sort_index < ? AND is_deleted = 0',
|
||||||
[safeNew, safeOld],
|
[safeNew, safeOld],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -167,6 +192,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
NotesCompanion(
|
NotesCompanion(
|
||||||
sortIndex: Value<int>(safeNew),
|
sortIndex: Value<int>(safeNew),
|
||||||
updatedAt: Value<DateTime>(DateTime.now()),
|
updatedAt: Value<DateTime>(DateTime.now()),
|
||||||
|
isDirty: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -179,8 +205,12 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<DbNote>> getDeletedNotes() {
|
Future<List<DbNote>> getDeletedNotes() {
|
||||||
return (select(notes)
|
return (select(notes)..where(
|
||||||
..where((n) => n.isDeleted.equals(true) & n.title.isNotValue('') & n.body.isNotValue('')))
|
(n) =>
|
||||||
|
n.isDeleted.equals(true) &
|
||||||
|
n.title.isNotValue('') &
|
||||||
|
n.body.isNotValue(''),
|
||||||
|
))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,15 +222,11 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
|
|
||||||
// ========== Sync helpers ==========
|
// ========== Sync helpers ==========
|
||||||
Future<List<DbNote>> getUnsyncedNotes() {
|
Future<List<DbNote>> getUnsyncedNotes() {
|
||||||
return (select(notes)
|
return (select(notes)..where((n) => n.isDirty.equals(true))).get();
|
||||||
..where((n) => n.isDeleted.equals(true) | n.serverVersion.equals(0)))
|
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<DbCategory>> getUnsyncedCategories() {
|
Future<List<DbCategory>> getUnsyncedCategories() {
|
||||||
return (select(categories)
|
return (select(categories)..where((c) => c.isDirty.equals(true))).get();
|
||||||
..where((c) => c.isDeleted.equals(true) | c.serverVersion.equals(0)))
|
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+228
-141
File diff suppressed because it is too large
Load Diff
+144
-82
@@ -35,9 +35,9 @@ class NoteRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Note> createNote(Note note) async {
|
Future<Note> createNote(Note note) async {
|
||||||
final int id = await _database.insertNoteAtTop(
|
await _database.insertNoteAtTop(
|
||||||
NotesCompanion.insert(
|
NotesCompanion.insert(
|
||||||
uuid: note.uuid,
|
id: note.id,
|
||||||
title: note.title,
|
title: note.title,
|
||||||
body: note.body,
|
body: note.body,
|
||||||
createdAt: note.createdAt,
|
createdAt: note.createdAt,
|
||||||
@@ -45,56 +45,77 @@ class NoteRepository {
|
|||||||
sortIndex: 0,
|
sortIndex: 0,
|
||||||
serverVersion: const Value(0),
|
serverVersion: const Value(0),
|
||||||
isDeleted: const Value(false),
|
isDeleted: const Value(false),
|
||||||
categoryId: const Value(null),
|
categoryId: Value(note.categoryId),
|
||||||
|
isDirty: const Value(true),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return note.copyWith(id: id, index: 0);
|
return note.copyWith(position: 0, isDirty: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Note> updateNote(Note note) async {
|
Future<Note> updateNote(Note note) async {
|
||||||
final int noteId =
|
final DbNote? existingNote = await (_database.select(
|
||||||
note.id ??
|
_database.notes,
|
||||||
(throw ArgumentError('Note id is required to update a note.'));
|
)..where((n) => n.id.equals(note.id))).getSingleOrNull();
|
||||||
|
|
||||||
|
final DbNote row =
|
||||||
|
existingNote ??
|
||||||
|
(throw ArgumentError('Note not found for id ${note.id}.'));
|
||||||
|
|
||||||
await _database.updateNoteRow(
|
await _database.updateNoteRow(
|
||||||
DbNote(
|
DbNote(
|
||||||
id: noteId,
|
id: row.id,
|
||||||
uuid: note.uuid,
|
|
||||||
title: note.title,
|
title: note.title,
|
||||||
body: note.body,
|
body: note.body,
|
||||||
createdAt: note.createdAt,
|
createdAt: row.createdAt,
|
||||||
updatedAt: note.updatedAt,
|
updatedAt: note.updatedAt,
|
||||||
sortIndex: note.index,
|
sortIndex: row.sortIndex,
|
||||||
serverVersion: note.serverVersion,
|
serverVersion: note.serverVersion,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
categoryId: note.categoryId,
|
categoryId: note.categoryId,
|
||||||
|
isDirty: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return note.copyWith(isDeleted: false, isPermanentlyDeleted: false);
|
return note.copyWith(
|
||||||
|
isDeleted: false,
|
||||||
|
isPermanentlyDeleted: false,
|
||||||
|
isDirty: true,
|
||||||
|
position: row.sortIndex.toDouble(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteNote(Note note) async {
|
Future<void> deleteNote(Note note) async {
|
||||||
final int noteId =
|
final DbNote? existingNote = await (_database.select(
|
||||||
note.id ??
|
_database.notes,
|
||||||
(throw ArgumentError('Note id is required to delete a note.'));
|
)..where((n) => n.id.equals(note.id))).getSingleOrNull();
|
||||||
|
|
||||||
if (note.isDeleted || note.isPermanentlyDeleted) {
|
final DbNote row =
|
||||||
await _database.permanentlyDeleteNote(noteId);
|
existingNote ??
|
||||||
|
(throw ArgumentError('Note not found for id ${note.id}.'));
|
||||||
|
|
||||||
|
if (row.isDeleted || note.isDeleted || note.isPermanentlyDeleted) {
|
||||||
|
await _database.permanentlyDeleteNote(row.id);
|
||||||
} else {
|
} else {
|
||||||
await _database.deleteNoteAndShift(id: noteId, removedIndex: note.index);
|
await _database.deleteNoteAndShift(
|
||||||
|
id: row.id,
|
||||||
|
removedIndex: row.sortIndex,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> moveNote(Note note, int newIndex) async {
|
Future<void> moveNote(Note note, int newIndex) async {
|
||||||
final int noteId =
|
final DbNote? existingNote = await (_database.select(
|
||||||
note.id ??
|
_database.notes,
|
||||||
(throw ArgumentError('Note id is required to reorder a note.'));
|
)..where((n) => n.id.equals(note.id))).getSingleOrNull();
|
||||||
|
|
||||||
|
final DbNote row =
|
||||||
|
existingNote ??
|
||||||
|
(throw ArgumentError('Note not found for id ${note.id}.'));
|
||||||
|
|
||||||
await _database.moveNote(
|
await _database.moveNote(
|
||||||
id: noteId,
|
id: row.id,
|
||||||
oldIndex: note.index,
|
oldIndex: row.sortIndex,
|
||||||
newIndex: newIndex,
|
newIndex: newIndex,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -120,26 +141,17 @@ class NoteRepository {
|
|||||||
? DateTime.utc(1970, 1, 1)
|
? DateTime.utc(1970, 1, 1)
|
||||||
: lastSync;
|
: lastSync;
|
||||||
|
|
||||||
// Collect pending local changes.
|
// Collect pending local changes. Dirty flags are the source of truth for
|
||||||
// If we already synced at least once, use updatedAt to avoid re-sending
|
// outbound sync; `lastSyncAt` is only used for asking the server what it
|
||||||
// old notes that were already uploaded.
|
// changed since our previous successful sync.
|
||||||
final List<DbNote> unsyncedNotes;
|
final List<DbNote> unsyncedNotes = await _database.getUnsyncedNotes();
|
||||||
final List<DbCategory> unsyncedCategories;
|
final List<DbCategory> unsyncedCategories = await _database
|
||||||
|
.getUnsyncedCategories();
|
||||||
if (forceFull || lastSync == null) {
|
|
||||||
unsyncedNotes = await _database.getUnsyncedNotes();
|
|
||||||
unsyncedCategories = await _database.getUnsyncedCategories();
|
|
||||||
} else {
|
|
||||||
unsyncedNotes = await _database.getNotesChangedSince(lastSync);
|
|
||||||
unsyncedCategories = await _database.getCategoriesChangedSince(
|
|
||||||
lastSync,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final int totalNotesToEncrypt = unsyncedNotes.length;
|
final int totalNotesToEncrypt = unsyncedNotes.length;
|
||||||
|
|
||||||
// Build sync request (note: we send encrypted data, but locally we have plaintext)
|
// Build sync request: local data is plaintext, encryption happens only
|
||||||
// Encrypt notes that still contain content before sending.
|
// for the outbound payload.
|
||||||
if (totalNotesToEncrypt == 0) {
|
if (totalNotesToEncrypt == 0) {
|
||||||
onProgress?.call(
|
onProgress?.call(
|
||||||
SyncStatus.encrypting,
|
SyncStatus.encrypting,
|
||||||
@@ -164,9 +176,8 @@ class NoteRepository {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<SyncCategoryPayload> categoriesPayload = unsyncedCategories
|
final List<SyncCategoryPayload> categoriesPayload =
|
||||||
.map((cat) => SyncCategoryPayload.fromCategory(_fromDbCategory(cat)))
|
await _encryptCategories(unsyncedCategories, masterKey: _masterKey);
|
||||||
.toList();
|
|
||||||
|
|
||||||
final SyncRequest syncRequest = SyncRequest(
|
final SyncRequest syncRequest = SyncRequest(
|
||||||
lastSyncAt: lastSyncForRequest,
|
lastSyncAt: lastSyncForRequest,
|
||||||
@@ -184,7 +195,27 @@ class NoteRepository {
|
|||||||
final Map<String, dynamic> syncResult = await _authApi.sync(syncRequest);
|
final Map<String, dynamic> syncResult = await _authApi.sync(syncRequest);
|
||||||
|
|
||||||
if (syncResult['error'] == true) {
|
if (syncResult['error'] == true) {
|
||||||
return {'error': true, 'message': syncResult['body']};
|
final List<String> details = [];
|
||||||
|
final Object? message = syncResult['message'];
|
||||||
|
final Object? exception = syncResult['exception'];
|
||||||
|
final Object? stackTrace = syncResult['stackTrace'];
|
||||||
|
final Object? body = syncResult['body'];
|
||||||
|
|
||||||
|
if (message != null) details.add(message.toString());
|
||||||
|
if (exception != null && exception.toString() != details.firstOrNull) {
|
||||||
|
details.add('Exception: ${exception.toString()}');
|
||||||
|
}
|
||||||
|
if (body != null) {
|
||||||
|
details.add('Body: ${body.toString()}');
|
||||||
|
}
|
||||||
|
if (stackTrace != null) {
|
||||||
|
details.add('StackTrace: ${stackTrace.toString()}');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'error': true,
|
||||||
|
'message': details.join('\n\n'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
final SyncResponse response = syncResult['data'] as SyncResponse;
|
final SyncResponse response = syncResult['data'] as SyncResponse;
|
||||||
@@ -217,8 +248,11 @@ class NoteRepository {
|
|||||||
'notesCount': response.changes.notes.length,
|
'notesCount': response.changes.notes.length,
|
||||||
'categoriesCount': response.changes.categories.length,
|
'categoriesCount': response.changes.categories.length,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
return {'error': true, 'message': e.toString()};
|
return {
|
||||||
|
'error': true,
|
||||||
|
'message': '$e\n\nStackTrace: $st',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,13 +263,23 @@ class NoteRepository {
|
|||||||
// Apply categories from server
|
// Apply categories from server
|
||||||
for (final SyncCategoryResponse catResponse
|
for (final SyncCategoryResponse catResponse
|
||||||
in response.changes.categories) {
|
in response.changes.categories) {
|
||||||
|
final String categoryName =
|
||||||
|
catResponse.isDeleted || catResponse.encryptedName.isEmpty
|
||||||
|
? ''
|
||||||
|
: await NoteEncryption.decryptNote(
|
||||||
|
catResponse.encryptedName,
|
||||||
|
_masterKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
await _database.upsertCategory(
|
await _database.upsertCategory(
|
||||||
CategoriesCompanion(
|
CategoriesCompanion(
|
||||||
uuid: Value(catResponse.id),
|
id: Value(catResponse.id),
|
||||||
encryptedName: Value(catResponse.encryptedName),
|
encryptedName: Value(categoryName),
|
||||||
serverVersion: Value(catResponse.serverVersion),
|
serverVersion: Value(catResponse.serverVersion),
|
||||||
isDeleted: Value(catResponse.isDeleted),
|
isDeleted: Value(catResponse.isDeleted),
|
||||||
updatedAt: Value(catResponse.updatedAt),
|
updatedAt: Value(catResponse.updatedAt),
|
||||||
|
isDirty: const Value(false),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -254,14 +298,14 @@ class NoteRepository {
|
|||||||
|
|
||||||
for (var index = 0; index < decryptedNotes.length; index += 1) {
|
for (var index = 0; index < decryptedNotes.length; index += 1) {
|
||||||
final Map<String, Object?> decryptedNote = decryptedNotes[index];
|
final Map<String, Object?> decryptedNote = decryptedNotes[index];
|
||||||
final String noteId = decryptedNote['id']! as String;
|
final SyncNoteResponse noteResponse = response.changes.notes[index];
|
||||||
|
final String noteId = (decryptedNote['id'] as String?) ?? noteResponse.id;
|
||||||
final existingNote = await (_database.select(
|
final existingNote = await (_database.select(
|
||||||
_database.notes,
|
_database.notes,
|
||||||
)..where((n) => n.uuid.equals(noteId))).getSingleOrNull();
|
)..where((n) => n.id.equals(noteId))).getSingleOrNull();
|
||||||
|
|
||||||
final String decryptedTitle = decryptedNote['title']! as String;
|
final String decryptedTitle = (decryptedNote['title'] as String?) ?? '';
|
||||||
final String decryptedBody = decryptedNote['body']! as String;
|
final String decryptedBody = (decryptedNote['body'] as String?) ?? '';
|
||||||
final SyncNoteResponse noteResponse = response.changes.notes[index];
|
|
||||||
final bool isPermanentlyDeleted = noteResponse.isPermanentlyDeleted;
|
final bool isPermanentlyDeleted = noteResponse.isPermanentlyDeleted;
|
||||||
|
|
||||||
if (existingNote != null) {
|
if (existingNote != null) {
|
||||||
@@ -269,15 +313,15 @@ class NoteRepository {
|
|||||||
await _database.updateNoteRow(
|
await _database.updateNoteRow(
|
||||||
DbNote(
|
DbNote(
|
||||||
id: existingNote.id,
|
id: existingNote.id,
|
||||||
uuid: noteId,
|
|
||||||
title: isPermanentlyDeleted ? '' : decryptedTitle,
|
title: isPermanentlyDeleted ? '' : decryptedTitle,
|
||||||
body: isPermanentlyDeleted ? '' : decryptedBody,
|
body: isPermanentlyDeleted ? '' : decryptedBody,
|
||||||
createdAt: existingNote.createdAt,
|
createdAt: existingNote.createdAt,
|
||||||
updatedAt: noteResponse.updatedAt,
|
updatedAt: noteResponse.updatedAt,
|
||||||
sortIndex: noteResponse.position,
|
sortIndex: noteResponse.position.round(),
|
||||||
serverVersion: noteResponse.serverVersion,
|
serverVersion: noteResponse.serverVersion,
|
||||||
isDeleted: noteResponse.isDeleted,
|
isDeleted: noteResponse.isDeleted,
|
||||||
categoryId: isPermanentlyDeleted ? null : noteResponse.categoryId,
|
categoryId: isPermanentlyDeleted ? null : noteResponse.categoryId,
|
||||||
|
isDirty: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -286,15 +330,18 @@ class NoteRepository {
|
|||||||
.into(_database.notes)
|
.into(_database.notes)
|
||||||
.insert(
|
.insert(
|
||||||
NotesCompanion(
|
NotesCompanion(
|
||||||
uuid: Value(noteResponse.id),
|
id: Value(noteResponse.id),
|
||||||
title: Value(isPermanentlyDeleted ? '' : decryptedTitle),
|
title: Value(isPermanentlyDeleted ? '' : decryptedTitle),
|
||||||
body: Value(isPermanentlyDeleted ? '' : decryptedBody),
|
body: Value(isPermanentlyDeleted ? '' : decryptedBody),
|
||||||
createdAt: Value(noteResponse.updatedAt),
|
createdAt: Value(noteResponse.updatedAt),
|
||||||
updatedAt: Value(noteResponse.updatedAt),
|
updatedAt: Value(noteResponse.updatedAt),
|
||||||
sortIndex: Value(noteResponse.position),
|
sortIndex: Value(noteResponse.position.round()),
|
||||||
serverVersion: Value(noteResponse.serverVersion),
|
serverVersion: Value(noteResponse.serverVersion),
|
||||||
isDeleted: Value(noteResponse.isDeleted),
|
isDeleted: Value(noteResponse.isDeleted),
|
||||||
categoryId: Value(isPermanentlyDeleted ? null : noteResponse.categoryId),
|
categoryId: Value(
|
||||||
|
isPermanentlyDeleted ? null : noteResponse.categoryId,
|
||||||
|
),
|
||||||
|
isDirty: const Value(false),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -314,26 +361,16 @@ class NoteRepository {
|
|||||||
Note _fromDbNote(DbNote row) {
|
Note _fromDbNote(DbNote row) {
|
||||||
return Note(
|
return Note(
|
||||||
id: row.id,
|
id: row.id,
|
||||||
uuid: row.uuid,
|
|
||||||
title: row.title,
|
title: row.title,
|
||||||
body: row.body,
|
body: row.body,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
updatedAt: row.updatedAt,
|
updatedAt: row.updatedAt,
|
||||||
index: row.sortIndex,
|
position: row.sortIndex.toDouble(),
|
||||||
serverVersion: row.serverVersion,
|
serverVersion: row.serverVersion,
|
||||||
isDeleted: row.isDeleted,
|
isDeleted: row.isDeleted,
|
||||||
isPermanentlyDeleted: _isPermanentlyDeleted(row),
|
isPermanentlyDeleted: _isPermanentlyDeleted(row),
|
||||||
categoryId: row.categoryId,
|
categoryId: row.categoryId,
|
||||||
);
|
isDirty: row.isDirty,
|
||||||
}
|
|
||||||
|
|
||||||
Category _fromDbCategory(DbCategory row) {
|
|
||||||
return Category(
|
|
||||||
uuid: row.uuid,
|
|
||||||
encryptedName: row.encryptedName,
|
|
||||||
serverVersion: row.serverVersion,
|
|
||||||
isDeleted: row.isDeleted,
|
|
||||||
updatedAt: row.updatedAt,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,6 +432,35 @@ Future<List<SyncNotePayload>> _encryptNotesInParallel(
|
|||||||
return orderedPayloads.cast<SyncNotePayload>();
|
return orderedPayloads.cast<SyncNotePayload>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<SyncCategoryPayload>> _encryptCategories(
|
||||||
|
List<DbCategory> categories, {
|
||||||
|
required String masterKey,
|
||||||
|
}) async {
|
||||||
|
final List<SyncCategoryPayload> payloads = [];
|
||||||
|
|
||||||
|
for (final DbCategory row in categories) {
|
||||||
|
final String encryptedName = row.encryptedName.isEmpty
|
||||||
|
? ''
|
||||||
|
: await NoteEncryption.encryptNote(row.encryptedName, masterKey);
|
||||||
|
|
||||||
|
payloads.add(
|
||||||
|
SyncCategoryPayload.fromCategory(
|
||||||
|
Category(
|
||||||
|
id: row.id,
|
||||||
|
name: row.encryptedName,
|
||||||
|
serverVersion: row.serverVersion,
|
||||||
|
isDeleted: row.isDeleted,
|
||||||
|
updatedAt: row.updatedAt,
|
||||||
|
isDirty: row.isDirty,
|
||||||
|
),
|
||||||
|
encryptedName: encryptedName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloads;
|
||||||
|
}
|
||||||
|
|
||||||
List<Future<List<Map<String, Object?>>>> _decryptNoteBatches(
|
List<Future<List<Map<String, Object?>>>> _decryptNoteBatches(
|
||||||
List<SyncNoteResponse> notes, {
|
List<SyncNoteResponse> notes, {
|
||||||
required String masterKey,
|
required String masterKey,
|
||||||
@@ -467,7 +533,7 @@ Map<String, Object?> _dbNoteToEncryptionInput(DbNote row, int index) {
|
|||||||
|
|
||||||
return <String, Object?>{
|
return <String, Object?>{
|
||||||
'index': index,
|
'index': index,
|
||||||
'uuid': row.uuid,
|
'id': row.id,
|
||||||
'title': row.title,
|
'title': row.title,
|
||||||
'body': row.body,
|
'body': row.body,
|
||||||
'createdAt': row.createdAt.toIso8601String(),
|
'createdAt': row.createdAt.toIso8601String(),
|
||||||
@@ -487,7 +553,7 @@ SyncNotePayload _syncNotePayloadFromEncryptionResult(Map<String, Object?> row) {
|
|||||||
encryptedTitle: row['encryptedTitle']! as String,
|
encryptedTitle: row['encryptedTitle']! as String,
|
||||||
encryptedBody: row['encryptedBody']! as String,
|
encryptedBody: row['encryptedBody']! as String,
|
||||||
serverVersion: row['serverVersion']! as int,
|
serverVersion: row['serverVersion']! as int,
|
||||||
position: row['position']! as int,
|
position: (row['position']! as num).toDouble(),
|
||||||
isDeleted: row['isDeleted']! as bool,
|
isDeleted: row['isDeleted']! as bool,
|
||||||
isPermanentlyDeleted: row['isPermanentlyDeleted']! as bool,
|
isPermanentlyDeleted: row['isPermanentlyDeleted']! as bool,
|
||||||
updatedAt: DateTime.parse(row['updatedAt']! as String),
|
updatedAt: DateTime.parse(row['updatedAt']! as String),
|
||||||
@@ -513,19 +579,13 @@ Future<List<Map<String, Object?>>> _encryptNoteBatch(
|
|||||||
encryptedTitle = '';
|
encryptedTitle = '';
|
||||||
encryptedBody = '';
|
encryptedBody = '';
|
||||||
} else {
|
} else {
|
||||||
encryptedTitle = await NoteEncryption.encryptNote(
|
encryptedTitle = await NoteEncryption.encryptNote(title, masterKey);
|
||||||
title,
|
encryptedBody = await NoteEncryption.encryptNote(body, masterKey);
|
||||||
masterKey,
|
|
||||||
);
|
|
||||||
encryptedBody = await NoteEncryption.encryptNote(
|
|
||||||
body,
|
|
||||||
masterKey,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedNotes.add(<String, Object?>{
|
encryptedNotes.add(<String, Object?>{
|
||||||
'index': note['index'] as int,
|
'index': note['index'] as int,
|
||||||
'id': note['uuid'] as String,
|
'id': note['id'] as String,
|
||||||
'categoryId': note['categoryId'] as String?,
|
'categoryId': note['categoryId'] as String?,
|
||||||
'encryptedTitle': encryptedTitle,
|
'encryptedTitle': encryptedTitle,
|
||||||
'encryptedBody': encryptedBody,
|
'encryptedBody': encryptedBody,
|
||||||
@@ -570,9 +630,11 @@ Future<List<Map<String, Object?>>> _decryptNoteBatch(
|
|||||||
decryptedBody = '';
|
decryptedBody = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String noteId = note['id']! as String;
|
||||||
|
|
||||||
decryptedNotes.add(<String, Object?>{
|
decryptedNotes.add(<String, Object?>{
|
||||||
'index': note['index'] as int,
|
'index': note['index'] as int,
|
||||||
'id': note['id'] as String,
|
'id': noteId,
|
||||||
'title': decryptedTitle,
|
'title': decryptedTitle,
|
||||||
'body': decryptedBody,
|
'body': decryptedBody,
|
||||||
'isPermanentlyDeleted': isPermanentlyDeleted,
|
'isPermanentlyDeleted': isPermanentlyDeleted,
|
||||||
|
|||||||
+23
-21
@@ -98,16 +98,19 @@ class SyncCategoryPayload {
|
|||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String id; // uuid
|
final String id;
|
||||||
final String encryptedName;
|
final String encryptedName;
|
||||||
final int serverVersion;
|
final int serverVersion;
|
||||||
final bool isDeleted;
|
final bool isDeleted;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
|
|
||||||
factory SyncCategoryPayload.fromCategory(Category category) {
|
factory SyncCategoryPayload.fromCategory(
|
||||||
|
Category category, {
|
||||||
|
required String encryptedName,
|
||||||
|
}) {
|
||||||
return SyncCategoryPayload(
|
return SyncCategoryPayload(
|
||||||
id: category.uuid,
|
id: category.id,
|
||||||
encryptedName: category.encryptedName,
|
encryptedName: encryptedName,
|
||||||
serverVersion: category.serverVersion,
|
serverVersion: category.serverVersion,
|
||||||
isDeleted: category.isDeleted,
|
isDeleted: category.isDeleted,
|
||||||
updatedAt: category.updatedAt,
|
updatedAt: category.updatedAt,
|
||||||
@@ -132,18 +135,18 @@ class SyncNotePayload {
|
|||||||
required this.encryptedTitle,
|
required this.encryptedTitle,
|
||||||
required this.encryptedBody,
|
required this.encryptedBody,
|
||||||
required this.serverVersion,
|
required this.serverVersion,
|
||||||
this.position = 0,
|
this.position = 0.0,
|
||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
this.isPermanentlyDeleted = false,
|
this.isPermanentlyDeleted = false,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String id; // uuid
|
final String id;
|
||||||
final String? categoryId;
|
final String? categoryId;
|
||||||
final String encryptedTitle;
|
final String encryptedTitle;
|
||||||
final String encryptedBody;
|
final String encryptedBody;
|
||||||
final int serverVersion;
|
final int serverVersion;
|
||||||
final int position;
|
final double position;
|
||||||
final bool isDeleted;
|
final bool isDeleted;
|
||||||
final bool isPermanentlyDeleted;
|
final bool isPermanentlyDeleted;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
@@ -155,12 +158,12 @@ class SyncNotePayload {
|
|||||||
bool isPermanentlyDeleted = false,
|
bool isPermanentlyDeleted = false,
|
||||||
}) {
|
}) {
|
||||||
return SyncNotePayload(
|
return SyncNotePayload(
|
||||||
id: note.uuid,
|
id: note.id,
|
||||||
categoryId: note.categoryId,
|
categoryId: note.categoryId,
|
||||||
encryptedTitle: encryptedTitle,
|
encryptedTitle: encryptedTitle,
|
||||||
encryptedBody: encryptedBody,
|
encryptedBody: encryptedBody,
|
||||||
serverVersion: note.serverVersion,
|
serverVersion: note.serverVersion,
|
||||||
position: note.index,
|
position: note.position,
|
||||||
isDeleted: note.isDeleted,
|
isDeleted: note.isDeleted,
|
||||||
isPermanentlyDeleted: isPermanentlyDeleted,
|
isPermanentlyDeleted: isPermanentlyDeleted,
|
||||||
updatedAt: note.updatedAt,
|
updatedAt: note.updatedAt,
|
||||||
@@ -212,8 +215,7 @@ class SyncCategoryResponse {
|
|||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
});
|
});
|
||||||
|
final String id;
|
||||||
final String id; // uuid
|
|
||||||
final String encryptedName;
|
final String encryptedName;
|
||||||
final int serverVersion;
|
final int serverVersion;
|
||||||
final bool isDeleted;
|
final bool isDeleted;
|
||||||
@@ -229,10 +231,10 @@ class SyncCategoryResponse {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Category toCategory() {
|
Category toCategory({required String name}) {
|
||||||
return Category(
|
return Category(
|
||||||
uuid: id,
|
id: id,
|
||||||
encryptedName: encryptedName,
|
name: name,
|
||||||
serverVersion: serverVersion,
|
serverVersion: serverVersion,
|
||||||
isDeleted: isDeleted,
|
isDeleted: isDeleted,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
@@ -247,18 +249,17 @@ class SyncNoteResponse {
|
|||||||
required this.encryptedTitle,
|
required this.encryptedTitle,
|
||||||
required this.encryptedBody,
|
required this.encryptedBody,
|
||||||
required this.serverVersion,
|
required this.serverVersion,
|
||||||
this.position = 0,
|
this.position = 0.0,
|
||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
this.isPermanentlyDeleted = false,
|
this.isPermanentlyDeleted = false,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
});
|
});
|
||||||
|
final String id;
|
||||||
final String id; // uuid
|
|
||||||
final String? categoryId;
|
final String? categoryId;
|
||||||
final String encryptedTitle;
|
final String encryptedTitle;
|
||||||
final String encryptedBody;
|
final String encryptedBody;
|
||||||
final int serverVersion;
|
final int serverVersion;
|
||||||
final int position;
|
final double position;
|
||||||
final bool isDeleted;
|
final bool isDeleted;
|
||||||
final bool isPermanentlyDeleted;
|
final bool isPermanentlyDeleted;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
@@ -272,7 +273,7 @@ class SyncNoteResponse {
|
|||||||
encryptedTitle: _readOptionalStringValue(json['encrypted_title']),
|
encryptedTitle: _readOptionalStringValue(json['encrypted_title']),
|
||||||
encryptedBody: _readOptionalStringValue(json['encrypted_body']),
|
encryptedBody: _readOptionalStringValue(json['encrypted_body']),
|
||||||
serverVersion: _readIntValue(json['serverVersion']),
|
serverVersion: _readIntValue(json['serverVersion']),
|
||||||
position: json['position'] as int? ?? 0,
|
position: (json['position'] as num?)?.toDouble() ?? 0,
|
||||||
isDeleted: json['isDeleted'] as bool? ?? false,
|
isDeleted: json['isDeleted'] as bool? ?? false,
|
||||||
isPermanentlyDeleted: json['isPermanentlyDeleted'] as bool? ?? false,
|
isPermanentlyDeleted: json['isPermanentlyDeleted'] as bool? ?? false,
|
||||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
@@ -281,15 +282,16 @@ class SyncNoteResponse {
|
|||||||
|
|
||||||
Note toNote() {
|
Note toNote() {
|
||||||
return Note(
|
return Note(
|
||||||
uuid: id,
|
id: id,
|
||||||
title: isPermanentlyDeleted ? '' : 'Encrypted',
|
title: isPermanentlyDeleted ? '' : 'Encrypted',
|
||||||
body: isPermanentlyDeleted ? '' : 'Encrypted',
|
body: isPermanentlyDeleted ? '' : 'Encrypted',
|
||||||
createdAt: updatedAt,
|
createdAt: updatedAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
index: position,
|
position: position,
|
||||||
serverVersion: serverVersion,
|
serverVersion: serverVersion,
|
||||||
isDeleted: isDeleted,
|
isDeleted: isDeleted,
|
||||||
categoryId: categoryId,
|
categoryId: categoryId,
|
||||||
|
isDirty: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-11
@@ -2,32 +2,36 @@ import 'package:uuid/uuid.dart';
|
|||||||
|
|
||||||
class Category {
|
class Category {
|
||||||
Category({
|
Category({
|
||||||
String? uuid,
|
String? id,
|
||||||
required this.encryptedName,
|
required this.name,
|
||||||
this.serverVersion = 0,
|
this.serverVersion = 0,
|
||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
}) : uuid = uuid ?? Uuid().v4();
|
this.isDirty = true,
|
||||||
|
}) : id = id ?? Uuid().v4();
|
||||||
|
|
||||||
final String uuid;
|
final String id;
|
||||||
final String encryptedName;
|
final String name;
|
||||||
final int serverVersion;
|
final int serverVersion;
|
||||||
final bool isDeleted;
|
final bool isDeleted;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
|
final bool isDirty;
|
||||||
|
|
||||||
Category copyWith({
|
Category copyWith({
|
||||||
String? uuid,
|
String? id,
|
||||||
String? encryptedName,
|
String? name,
|
||||||
int? serverVersion,
|
int? serverVersion,
|
||||||
bool? isDeleted,
|
bool? isDeleted,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
|
bool? isDirty,
|
||||||
}) {
|
}) {
|
||||||
return Category(
|
return Category(
|
||||||
uuid: uuid ?? this.uuid,
|
id: id ?? this.id,
|
||||||
encryptedName: encryptedName ?? this.encryptedName,
|
name: name ?? this.name,
|
||||||
serverVersion: serverVersion ?? this.serverVersion,
|
serverVersion: serverVersion ?? this.serverVersion,
|
||||||
isDeleted: isDeleted ?? this.isDeleted,
|
isDeleted: isDeleted ?? this.isDeleted,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
isDirty: isDirty ?? this.isDirty,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +41,9 @@ class Category {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return other is Category && uuid == other.uuid;
|
return other is Category && id == other.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => uuid.hashCode;
|
int get hashCode => id.hashCode;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-24
@@ -2,63 +2,58 @@ import 'package:uuid/uuid.dart';
|
|||||||
|
|
||||||
// Model: Note
|
// Model: Note
|
||||||
// - Representa una nota guardada en la app.
|
// - Representa una nota guardada en la app.
|
||||||
// - `id` es el identificador local de SQLite (autoincrement).
|
|
||||||
// - `uuid` es el identificador global sincronizado con el servidor.
|
|
||||||
// - `index` representa el orden visual dentro de la lista.
|
|
||||||
// - `serverVersion` se usa para resolver conflictos en sync.
|
|
||||||
// - `isDeleted` marca eliminaciones blandas.
|
|
||||||
class Note {
|
class Note {
|
||||||
Note({
|
Note({
|
||||||
this.id,
|
String? id,
|
||||||
String? uuid,
|
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.body,
|
required this.body,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
required this.index,
|
required this.position,
|
||||||
|
this.categoryId,
|
||||||
this.serverVersion = 0,
|
this.serverVersion = 0,
|
||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
this.isPermanentlyDeleted = false,
|
this.isPermanentlyDeleted = false,
|
||||||
this.categoryId,
|
this.isDirty = true,
|
||||||
}) : uuid = uuid ?? Uuid().v4();
|
}) : id = id ?? Uuid().v4();
|
||||||
|
|
||||||
final int? id;
|
final String id;
|
||||||
final String uuid;
|
|
||||||
final String title;
|
final String title;
|
||||||
final String body;
|
final String body;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
final int index;
|
final double position;
|
||||||
|
final String? categoryId;
|
||||||
final int serverVersion;
|
final int serverVersion;
|
||||||
final bool isDeleted;
|
final bool isDeleted;
|
||||||
final bool isPermanentlyDeleted;
|
final bool isPermanentlyDeleted;
|
||||||
final String? categoryId;
|
final bool isDirty;
|
||||||
|
|
||||||
Note copyWith({
|
Note copyWith({
|
||||||
int? id,
|
String? id,
|
||||||
String? uuid,
|
|
||||||
String? title,
|
String? title,
|
||||||
String? body,
|
String? body,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
int? index,
|
double? position,
|
||||||
|
String? categoryId,
|
||||||
int? serverVersion,
|
int? serverVersion,
|
||||||
bool? isDeleted,
|
bool? isDeleted,
|
||||||
bool? isPermanentlyDeleted,
|
bool? isPermanentlyDeleted,
|
||||||
String? categoryId,
|
bool? isDirty,
|
||||||
}) {
|
}) {
|
||||||
return Note(
|
return Note(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
uuid: uuid ?? this.uuid,
|
|
||||||
title: title ?? this.title,
|
title: title ?? this.title,
|
||||||
body: body ?? this.body,
|
body: body ?? this.body,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
index: index ?? this.index,
|
position: position ?? this.position,
|
||||||
|
categoryId: categoryId ?? this.categoryId,
|
||||||
serverVersion: serverVersion ?? this.serverVersion,
|
serverVersion: serverVersion ?? this.serverVersion,
|
||||||
isDeleted: isDeleted ?? this.isDeleted,
|
isDeleted: isDeleted ?? this.isDeleted,
|
||||||
isPermanentlyDeleted: isPermanentlyDeleted ?? this.isPermanentlyDeleted,
|
isPermanentlyDeleted: isPermanentlyDeleted ?? this.isPermanentlyDeleted,
|
||||||
categoryId: categoryId ?? this.categoryId,
|
isDirty: isDirty ?? this.isDirty,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +63,9 @@ class Note {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return other is Note && uuid == other.uuid;
|
return other is Note && id == other.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => uuid.hashCode;
|
int get hashCode => id.hashCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,9 +154,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
// Don't let DB errors cause the app to reset the vault automatically.
|
// Don't let DB errors cause the app to reset the vault automatically.
|
||||||
debugPrint('Failed to move note: $e\n$st');
|
debugPrint('Failed to move note: $e\n$st');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text('Error al reordenar la nota: $e')),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text('Error al reordenar la nota: $e')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,9 +385,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: NoteCard(
|
child: NoteCard(
|
||||||
key: ValueKey<int>(
|
key: ValueKey<String>(
|
||||||
filteredNotes[index].id ??
|
filteredNotes[index].id,
|
||||||
filteredNotes[index].index,
|
|
||||||
),
|
),
|
||||||
note: filteredNotes[index],
|
note: filteredNotes[index],
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
@@ -510,9 +509,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: NoteCard(
|
child: NoteCard(
|
||||||
key: ValueKey<int>(
|
key: ValueKey<String>(
|
||||||
filteredNotes[index].id ??
|
filteredNotes[index].id,
|
||||||
filteredNotes[index].index,
|
|
||||||
),
|
),
|
||||||
note: filteredNotes[index],
|
note: filteredNotes[index],
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_isNewNote = widget.note?.id == null;
|
_isNewNote = widget.note == null;
|
||||||
|
|
||||||
if (_isNewNote) {
|
if (_isNewNote) {
|
||||||
final DateTime now = DateTime.now();
|
final DateTime now = DateTime.now();
|
||||||
@@ -98,7 +98,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
|
|||||||
body: '',
|
body: '',
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
index: 0,
|
position: 0,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_currentNote = widget.note!;
|
_currentNote = widget.note!;
|
||||||
@@ -143,6 +143,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
|
|||||||
title: title.isEmpty ? 'Sin título' : title,
|
title: title.isEmpty ? 'Sin título' : title,
|
||||||
body: body,
|
body: body,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
|
isDirty: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
_complete(updatedNote);
|
_complete(updatedNote);
|
||||||
|
|||||||
Reference in New Issue
Block a user