feat: add formula screen with unit conversion
This commit is contained in:
parent
45a9a89c3e
commit
fe89630f53
1 changed files with 227 additions and 0 deletions
227
lib/ai/formula_screen.dart
Normal file
227
lib/ai/formula_screen.dart
Normal file
|
|
@ -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<FormulaScreen> createState() => _FormulaScreenState();
|
||||
}
|
||||
|
||||
class _FormulaScreenState extends State<FormulaScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final Map<String, TextEditingController> _inputControllers = {};
|
||||
final Map<String, String?> _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 = <String, dynamic>{};
|
||||
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;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue