diff --git a/lib/ai/formula_screen.dart b/lib/ai/formula_screen.dart new file mode 100644 index 0000000..502af7e --- /dev/null +++ b/lib/ai/formula_screen.dart @@ -0,0 +1,227 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../formula_models.dart'; +import '../formula_evaluator.dart'; +import '../corpus.dart'; +import 'unit_dropdown.dart'; + +class FormulaScreen extends StatefulWidget { + final Formula formula; + final UnitCorpus corpus; + + const FormulaScreen({ + super.key, + required this.formula, + required this.corpus, + }); + + @override + State createState() => _FormulaScreenState(); +} + +class _FormulaScreenState extends State { + final _formKey = GlobalKey(); + final Map _inputControllers = {}; + final Map _selectedUnits = {}; + String? _result; + String? _selectedOutputUnit; + + @override + void initState() { + super.initState(); + // Initialize controllers and units + for (final input in widget.formula.input) { + _inputControllers[input.name] = TextEditingController(); + _selectedUnits[input.name] = input.magnitude; + } + _selectedOutputUnit = widget.formula.output.magnitude; + } + + @override + void dispose() { + // Clean up controllers + for (final controller in _inputControllers.values) { + controller.dispose(); + } + super.dispose(); + } + + void _evaluateFormula() { + if (!_formKey.currentState!.validate()) return; + + try { + final inputValues = {}; + for (final input in widget.formula.input) { + final value = double.tryParse(_inputControllers[input.name]!.text) ?? 0.0; + + // Convert input to base unit if needed + if (_selectedUnits[input.name] != input.magnitude) { + inputValues[input.name] = widget.corpus.convert( + value, + _selectedUnits[input.name]!, + input.magnitude, + ); + } else { + inputValues[input.name] = value; + } + } + + final evaluator = FormulaEvaluator(); + final result = evaluator.evaluate(widget.formula, inputValues); + + // Convert output to selected unit if needed + if (_selectedOutputUnit != widget.formula.output.magnitude) { + _result = widget.corpus.convert( + result, + widget.formula.output.magnitude, + _selectedOutputUnit!, + ).toStringAsFixed(2); + } else { + _result = result.toStringAsFixed(2); + } + + setState(() {}); + } catch (e) { + e + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Evaluation error: ${e.toString()}'), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.formula.name), + ), + body: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Expanded( + child: ListView( + children: [ + _buildInputSection(), + const SizedBox(height: 24), + _buildOutputSection(), + ], + ), + ), + ElevatedButton( + onPressed: _evaluateFormula, + child: const Text('Calculate'), + ), + ], + ), + ), + ), + ); + } + + Widget _buildInputSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Input Variables', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + ...widget.formula.input.map((variable) => _buildVariableRow(variable)), + ], + ); + } + + Widget _buildOutputSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Result', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Text(widget.formula.output.name), + const Spacer(), + SizedBox( + width: 100, + child: TextFormField( + readOnly: true, + controller: TextEditingController(text: _result), + decoration: const InputDecoration( + border: UnderlineInputBorder(), + ), + ), + ), + const SizedBox(width: 8), + UnitDropdown( + corpus: widget.corpus, + variable: widget.formula.output, + selectedUnit: _selectedOutputUnit, + onUnitChanged: (unit) { + setState(() { + _selectedOutputUnit = unit; + }); + }, + ), + ], + ), + ], + ); + } + + Widget _buildVariableRow(VariableSpec variable) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Text(variable.name), + const Spacer(), + SizedBox( + width: 100, + child: TextFormField( + controller: _inputControllers[variable.name], + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9\.\-]')), + ], + decoration: const InputDecoration( + border: UnderlineInputBorder(), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Required'; + } + return null; + }, + ), + ), + const SizedBox(width: 8), + UnitDropdown( + corpus: widget.corpus, + variable: variable, + selectedUnit: _selectedUnits[variable.name], + onUnitChanged: (unit) { + setState(() { + _selectedUnits[variable.name] = unit; + }); + }, + ), + ], + ), + ); + } +} \ No newline at end of file