From 5967d951ce9950d6675ce78472eee1b9f8178b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Gonz=C3=A1lez?= Date: Sun, 5 Apr 2026 17:45:01 +0200 Subject: [PATCH] Unified id and uuid. --- TODO.md | 5 +- lib/ai/import_preview_screen.dart | 33 ++++--- lib/database/database_service.dart | 63 ++++--------- lib/database/formulas_database.dart | 29 +++--- lib/database/formulas_database.g.dart | 131 +++++++++++++++----------- lib/formula_models.dart | 22 +++-- 6 files changed, 149 insertions(+), 134 deletions(-) diff --git a/TODO.md b/TODO.md index 0a6c8dd..f044b2d 100644 --- a/TODO.md +++ b/TODO.md @@ -80,9 +80,10 @@ - The "paste" button will copy the clipboard into the text editor. - A second button "import" will use the import preview screen -[R] Launch test app_test.dart. Iterate until the test pass. -- [ ] Unify UUID and id of FormulaElement +- [R] Unify UUID and id of FormulaElement - UUID is in memory - id is in database - remove id from database, add UUID to database -- [ ] Make formulaSolver() asyncronous, and show a CircularProgressIndicator while the formula is being solved. Honor a new optinal parameter "timeout" in formulaSolver, that will throw a TimeoutException. +- [ ] Solve exception in _CorpusLoaderState.build() when GetIt.instance.registerSingleton(corpus) after importing formula, since there is already registeted. - [ ] When importing FormulaElements, save the FormulaElements in the database (currently, they are only added to the Corpus in memory). +- [ ] Make formulaSolver() asyncronous, and show a CircularProgressIndicator while the formula is being solved. Honor a new optinal parameter "timeout" in formulaSolver, that will throw a TimeoutException. diff --git a/lib/ai/import_preview_screen.dart b/lib/ai/import_preview_screen.dart index 62ce1f4..d4326f8 100644 --- a/lib/ai/import_preview_screen.dart +++ b/lib/ai/import_preview_screen.dart @@ -4,6 +4,7 @@ import 'package:d4rt_formulas/formula_models.dart'; import 'package:d4rt_formulas/corpus.dart'; import 'package:d4rt_formulas/ai/formula_editor.dart'; import 'package:d4rt_formulas/services/import_service.dart'; +import 'package:d4rt_formulas/service_locator.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_highlight/themes/monokai-sublime.dart'; @@ -28,11 +29,7 @@ class _ImportPreviewScreenState extends State { super.initState(); // Select all by default for (final element in widget.elements) { - if (element is Formula) { - _selectedUuids.add(element.uuid); - } else if (element is UnitSpec) { - _selectedUuids.add(element.name); - } + _selectedUuids.add(element.uuid); } } @@ -59,14 +56,9 @@ class _ImportPreviewScreenState extends State { } } - void _importSelected() { + Future _importSelected() async { final selectedElements = widget.elements.where((element) { - if (element is Formula) { - return _selectedUuids.contains(element.uuid); - } else if (element is UnitSpec) { - return _selectedUuids.contains(element.name); - } - return false; + return _selectedUuids.contains(element.uuid); }).toList(); if (selectedElements.isEmpty) { @@ -79,6 +71,17 @@ class _ImportPreviewScreenState extends State { try { widget.corpus.loadFormulaElements(selectedElements, true); + // Save imported elements to the database + final database = getDatabase(); + for (final element in selectedElements) { + final existingElement = await database.getFormulaElementByUuid(element.uuid); + if (existingElement != null) { + await database.updateFormulaElement(element.uuid, element.toStringLiteral()); + } else { + await database.insertFormulaElement(element.uuid, element.toStringLiteral()); + } + } + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Imported ${selectedElements.length} element(s) successfully'), @@ -186,7 +189,7 @@ class _ImportPreviewScreenState extends State { } Widget _buildUnitTile(UnitSpec unit) { - final isSelected = _selectedUuids.contains(unit.name); + final isSelected = _selectedUuids.contains(unit.uuid); return Card( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), @@ -196,9 +199,9 @@ class _ImportPreviewScreenState extends State { onChanged: (value) { setState(() { if (value == true) { - _selectedUuids.add(unit.name); + _selectedUuids.add(unit.uuid); } else { - _selectedUuids.remove(unit.name); + _selectedUuids.remove(unit.uuid); } }); }, diff --git a/lib/database/database_service.dart b/lib/database/database_service.dart index 1767518..1e0c7e4 100644 --- a/lib/database/database_service.dart +++ b/lib/database/database_service.dart @@ -31,64 +31,39 @@ extension CorpusDatabaseExtension on FormulasDatabase { // Clear existing elements first await delete(formulaElements).go(); - // Insert new elements + // Insert new elements with their UUIDs for (final element in elements) { - await insertFormulaElement(element.toStringLiteral()); + await insertFormulaElement(element.uuid, element.toStringLiteral()); } } // Method to update a formula in the database by UUID Future updateFormula(models.Formula formula) async { - final elements = await getAllFormulaElements(); - - for (final element in elements) { - try { - final parsed = SetUtils.parseCorpusElements('[${element.elementText}]'); - if (parsed.isNotEmpty && parsed.first is models.Formula) { - final existingFormula = parsed.first as models.Formula; - if (existingFormula.uuid == formula.uuid) { - // Update this element - await updateFormulaElement( - element.id, - formula.toStringLiteral() - ); - return true; - } - } - } catch (e) { - print('Error parsing database element during update: $e'); - continue; - } + final existingElement = await getFormulaElementByUuid(formula.uuid); + if (existingElement != null) { + await updateFormulaElement(formula.uuid, formula.toStringLiteral()); + return true; } - - return false; // Formula not found + return false; } // Method to add a new formula to the database Future addFormula(models.Formula formula) async { - await insertFormulaElement(formula.toStringLiteral()); + await insertFormulaElement(formula.uuid, formula.toStringLiteral()); } - // Method to delete a formula from the database by name + // Method to add a new formula element (formula or unit) to the database + Future addFormulaElement(models.FormulaElement element) async { + await insertFormulaElement(element.uuid, element.toStringLiteral()); + } + + // Method to delete a formula from the database by UUID Future deleteFormula(String uuid) async { - final elements = await getAllFormulaElements(); - - for (final element in elements) { - try { - final parsed = SetUtils.parseCorpusElements('[${element.elementText}]'); - if (parsed.isNotEmpty && parsed.first is models.Formula) { - final existingFormula = parsed.first as models.Formula; - if (existingFormula.uuid == uuid) { - await deleteFormulaElement(element.id); - return true; - } - } - } catch (e) { - print('Error parsing database element during delete: $e'); - continue; - } + final existingElement = await getFormulaElementByUuid(uuid); + if (existingElement != null) { + await deleteFormulaElement(uuid); + return true; } - - return false; // Formula not found + return false; } } diff --git a/lib/database/formulas_database.dart b/lib/database/formulas_database.dart index e63c751..132e859 100644 --- a/lib/database/formulas_database.dart +++ b/lib/database/formulas_database.dart @@ -7,10 +7,13 @@ if (dart.library.ffi) 'formulas_database_native.dart'; part 'formulas_database.g.dart'; -// Define the FORMULAELEMENT table to store both formulas and units as text + class FormulaElements extends Table { - IntColumn get id => integer().autoIncrement()(); + TextColumn get uuid => text()(); TextColumn get elementText => text()(); + + @override + Set get primaryKey => {uuid}; } @DriftDatabase(tables: [FormulaElements]) @@ -21,8 +24,10 @@ class FormulasDatabase extends _$FormulasDatabase { int get schemaVersion => 1; // Method to insert a new formula element (either formula or unit) - Future insertFormulaElement(String elementText) { - return into(formulaElements).insert(FormulaElementsCompanion.insert(elementText: elementText)); + Future insertFormulaElement(String uuid, String elementText) { + return into(formulaElements).insert( + FormulaElementsCompanion.insert(uuid: uuid, elementText: elementText), + ); } // Method to get all formula elements @@ -30,20 +35,20 @@ class FormulasDatabase extends _$FormulasDatabase { return select(formulaElements).get(); } - // Method to get a formula element by ID - Future getFormulaElementById(int id) { - return (select(formulaElements)..where((tbl) => tbl.id.equals(id))).getSingleOrNull(); + // Method to get a formula element by UUID + Future getFormulaElementByUuid(String uuid) { + return (select(formulaElements)..where((tbl) => tbl.uuid.equals(uuid))).getSingleOrNull(); } // Method to update a formula element - Future updateFormulaElement(int id, String newElementText) { - return (update(formulaElements)..where((tbl) => tbl.id.equals(id))) - .write(FormulaElementsCompanion.insert(elementText: newElementText)); + Future updateFormulaElement(String uuid, String newElementText) { + return (update(formulaElements)..where((tbl) => tbl.uuid.equals(uuid))) + .write(FormulaElementsCompanion(elementText: Value(newElementText))); } // Method to delete a formula element - Future deleteFormulaElement(int id) { - return (delete(formulaElements)..where((tbl) => tbl.id.equals(id))).go(); + Future deleteFormulaElement(String uuid) { + return (delete(formulaElements)..where((tbl) => tbl.uuid.equals(uuid))).go(); } // Additional helper methods for direct access to the table diff --git a/lib/database/formulas_database.g.dart b/lib/database/formulas_database.g.dart index 1153d1c..60431be 100644 --- a/lib/database/formulas_database.g.dart +++ b/lib/database/formulas_database.g.dart @@ -9,18 +9,14 @@ class $FormulaElementsTable extends FormulaElements final GeneratedDatabase attachedDatabase; final String? _alias; $FormulaElementsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); + static const VerificationMeta _uuidMeta = const VerificationMeta('uuid'); @override - late final GeneratedColumn id = GeneratedColumn( - 'id', + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'PRIMARY KEY AUTOINCREMENT', - ), + type: DriftSqlType.string, + requiredDuringInsert: true, ); static const VerificationMeta _elementTextMeta = const VerificationMeta( 'elementText', @@ -34,7 +30,7 @@ class $FormulaElementsTable extends FormulaElements requiredDuringInsert: true, ); @override - List get $columns => [id, elementText]; + List get $columns => [uuid, elementText]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -47,8 +43,13 @@ class $FormulaElementsTable extends FormulaElements }) { final context = VerificationContext(); final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + if (data.containsKey('uuid')) { + context.handle( + _uuidMeta, + uuid.isAcceptableOrUnknown(data['uuid']!, _uuidMeta), + ); + } else if (isInserting) { + context.missing(_uuidMeta); } if (data.containsKey('element_text')) { context.handle( @@ -65,14 +66,14 @@ class $FormulaElementsTable extends FormulaElements } @override - Set get $primaryKey => {id}; + Set get $primaryKey => {uuid}; @override FormulaElement map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return FormulaElement( - id: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}id'], + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], )!, elementText: attachedDatabase.typeMapping.read( DriftSqlType.string, @@ -88,20 +89,20 @@ class $FormulaElementsTable extends FormulaElements } class FormulaElement extends DataClass implements Insertable { - final int id; + final String uuid; final String elementText; - const FormulaElement({required this.id, required this.elementText}); + const FormulaElement({required this.uuid, required this.elementText}); @override Map toColumns(bool nullToAbsent) { final map = {}; - map['id'] = Variable(id); + map['uuid'] = Variable(uuid); map['element_text'] = Variable(elementText); return map; } FormulaElementsCompanion toCompanion(bool nullToAbsent) { return FormulaElementsCompanion( - id: Value(id), + uuid: Value(uuid), elementText: Value(elementText), ); } @@ -112,7 +113,7 @@ class FormulaElement extends DataClass implements Insertable { }) { serializer ??= driftRuntimeOptions.defaultSerializer; return FormulaElement( - id: serializer.fromJson(json['id']), + uuid: serializer.fromJson(json['uuid']), elementText: serializer.fromJson(json['elementText']), ); } @@ -120,18 +121,19 @@ class FormulaElement extends DataClass implements Insertable { Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'id': serializer.toJson(id), + 'uuid': serializer.toJson(uuid), 'elementText': serializer.toJson(elementText), }; } - FormulaElement copyWith({int? id, String? elementText}) => FormulaElement( - id: id ?? this.id, - elementText: elementText ?? this.elementText, - ); + FormulaElement copyWith({String? uuid, String? elementText}) => + FormulaElement( + uuid: uuid ?? this.uuid, + elementText: elementText ?? this.elementText, + ); FormulaElement copyWithCompanion(FormulaElementsCompanion data) { return FormulaElement( - id: data.id.present ? data.id.value : this.id, + uuid: data.uuid.present ? data.uuid.value : this.uuid, elementText: data.elementText.present ? data.elementText.value : this.elementText, @@ -141,70 +143,82 @@ class FormulaElement extends DataClass implements Insertable { @override String toString() { return (StringBuffer('FormulaElement(') - ..write('id: $id, ') + ..write('uuid: $uuid, ') ..write('elementText: $elementText') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, elementText); + int get hashCode => Object.hash(uuid, elementText); @override bool operator ==(Object other) => identical(this, other) || (other is FormulaElement && - other.id == this.id && + other.uuid == this.uuid && other.elementText == this.elementText); } class FormulaElementsCompanion extends UpdateCompanion { - final Value id; + final Value uuid; final Value elementText; + final Value rowid; const FormulaElementsCompanion({ - this.id = const Value.absent(), + this.uuid = const Value.absent(), this.elementText = const Value.absent(), + this.rowid = const Value.absent(), }); FormulaElementsCompanion.insert({ - this.id = const Value.absent(), + required String uuid, required String elementText, - }) : elementText = Value(elementText); + this.rowid = const Value.absent(), + }) : uuid = Value(uuid), + elementText = Value(elementText); static Insertable custom({ - Expression? id, + Expression? uuid, Expression? elementText, + Expression? rowid, }) { return RawValuesInsertable({ - if (id != null) 'id': id, + if (uuid != null) 'uuid': uuid, if (elementText != null) 'element_text': elementText, + if (rowid != null) 'rowid': rowid, }); } FormulaElementsCompanion copyWith({ - Value? id, + Value? uuid, Value? elementText, + Value? rowid, }) { return FormulaElementsCompanion( - id: id ?? this.id, + uuid: uuid ?? this.uuid, elementText: elementText ?? this.elementText, + rowid: rowid ?? this.rowid, ); } @override Map toColumns(bool nullToAbsent) { final map = {}; - if (id.present) { - map['id'] = Variable(id.value); + if (uuid.present) { + map['uuid'] = Variable(uuid.value); } if (elementText.present) { map['element_text'] = Variable(elementText.value); } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } return map; } @override String toString() { return (StringBuffer('FormulaElementsCompanion(') - ..write('id: $id, ') - ..write('elementText: $elementText') + ..write('uuid: $uuid, ') + ..write('elementText: $elementText, ') + ..write('rowid: $rowid') ..write(')')) .toString(); } @@ -225,13 +239,15 @@ abstract class _$FormulasDatabase extends GeneratedDatabase { typedef $$FormulaElementsTableCreateCompanionBuilder = FormulaElementsCompanion Function({ - Value id, + required String uuid, required String elementText, + Value rowid, }); typedef $$FormulaElementsTableUpdateCompanionBuilder = FormulaElementsCompanion Function({ - Value id, + Value uuid, Value elementText, + Value rowid, }); class $$FormulaElementsTableFilterComposer @@ -243,8 +259,8 @@ class $$FormulaElementsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, + ColumnFilters get uuid => $composableBuilder( + column: $table.uuid, builder: (column) => ColumnFilters(column), ); @@ -263,8 +279,8 @@ class $$FormulaElementsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, + ColumnOrderings get uuid => $composableBuilder( + column: $table.uuid, builder: (column) => ColumnOrderings(column), ); @@ -283,8 +299,8 @@ class $$FormulaElementsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get uuid => + $composableBuilder(column: $table.uuid, builder: (column) => column); GeneratedColumn get elementText => $composableBuilder( column: $table.elementText, @@ -329,16 +345,23 @@ class $$FormulaElementsTableTableManager $$FormulaElementsTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ - Value id = const Value.absent(), + Value uuid = const Value.absent(), Value elementText = const Value.absent(), - }) => FormulaElementsCompanion(id: id, elementText: elementText), + Value rowid = const Value.absent(), + }) => FormulaElementsCompanion( + uuid: uuid, + elementText: elementText, + rowid: rowid, + ), createCompanionCallback: ({ - Value id = const Value.absent(), + required String uuid, required String elementText, + Value rowid = const Value.absent(), }) => FormulaElementsCompanion.insert( - id: id, + uuid: uuid, elementText: elementText, + rowid: rowid, ), withReferenceMapper: (p0) => p0 .map((e) => (e.readTable(table), BaseReferences(db, table, e))) diff --git a/lib/formula_models.dart b/lib/formula_models.dart index b42f037..584c31e 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -7,8 +7,12 @@ import 'package:uuid/uuid.dart'; typedef Number = double; +String _generateUuidV4() => Uuid().v4(); + /// Abstract base class for formula elements abstract class FormulaElement { + String get uuid; + Map toMap(); String toStringLiteral() { @@ -18,6 +22,8 @@ abstract class FormulaElement { } class UnitSpec extends FormulaElement { + @override + final String uuid; final String name; final String baseUnit; final String symbol; @@ -28,6 +34,7 @@ class UnitSpec extends FormulaElement { @override Map toMap() { return { + 'uuid': uuid, "name": name, "baseUnit": baseUnit, "symbol": symbol, @@ -38,32 +45,35 @@ class UnitSpec extends FormulaElement { } UnitSpec({ + String? uuid, required this.name, required this.baseUnit, required this.symbol, this.factorFromUnitToBase, this.codeFromBaseToUnit, this.codeFromUnitToBase, - }); + }) : uuid = uuid ?? _generateUuidV4(); factory UnitSpec.fromSet(Map theSet) { + String? uuid = theSet['uuid'] as String?; String name = SetUtils.stringValue(theSet, "name"); String symbol = SetUtils.stringValue(theSet, "symbol"); if (theSet.containsKey("isBase")) { - return UnitSpec(name: name, baseUnit: name, symbol: symbol, factorFromUnitToBase: 1); + return UnitSpec(uuid: uuid, name: name, baseUnit: name, symbol: symbol, factorFromUnitToBase: 1); } String baseUnit = SetUtils.stringValue(theSet, "baseUnit"); if (theSet.containsKey("factor")) { Number factorFromUnitToBase = SetUtils.numberValue(theSet, "factor"); - return UnitSpec(name: name, baseUnit: baseUnit, symbol: symbol, factorFromUnitToBase: factorFromUnitToBase); + return UnitSpec(uuid: uuid, name: name, baseUnit: baseUnit, symbol: symbol, factorFromUnitToBase: factorFromUnitToBase); } else if (theSet.containsKey("toBase")) { String codeFromBaseToUnit = SetUtils.stringValue(theSet, "fromBase"); String codeFromUnitToBase = SetUtils.stringValue(theSet, "toBase"); return UnitSpec( + uuid: uuid, name: name, baseUnit: baseUnit, symbol: symbol, @@ -84,7 +94,7 @@ class UnitSpec extends FormulaElement { } } -class VariableSpec extends FormulaElement { +class VariableSpec{ final String name; final String? unit; final List? values; @@ -98,7 +108,7 @@ class VariableSpec extends FormulaElement { }; } - VariableSpec({required this.name, this.unit, this.values}) { + VariableSpec({required this.name, this.unit, this.values}){ validate(); } @@ -128,8 +138,6 @@ class VariableSpec extends FormulaElement { int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0); } -String _generateUuidV4() => Uuid().v4(); - abstract class FormulaInterface { String get uuid;