d4t_formulas/lib/ai/import_preview_screen.dart

334 lines
10 KiB
Dart
Raw Normal View History

2026-03-15 10:34:04 +00:00
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
2026-03-15 10:34:04 +00:00
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:flutter_code_editor/flutter_code_editor.dart';
import 'package:flutter_highlight/themes/monokai-sublime.dart';
2026-03-17 18:30:15 +00:00
import 'package:highlight/languages/dart.dart';
2026-03-15 10:34:04 +00:00
/// Screen to preview and import formula elements
class ImportPreviewScreen extends StatefulWidget {
final List<FormulaElement> elements;
final Corpus corpus;
2026-03-19 11:05:10 +00:00
const ImportPreviewScreen({super.key, required this.elements, required this.corpus});
2026-03-15 10:34:04 +00:00
@override
State<ImportPreviewScreen> createState() => _ImportPreviewScreenState();
}
class _ImportPreviewScreenState extends State<ImportPreviewScreen> {
final Set<String> _selectedUuids = {};
@override
void initState() {
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);
}
}
}
void _editFormulaElement(FormulaElement element) {
if (element is Formula) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FormulaEditor(
formula: element,
corpus: widget.corpus,
onSave: (updatedFormula) {
// Update the element in the list
setState(() {
2026-03-19 11:05:10 +00:00
final index = widget.elements.indexWhere((e) => e is Formula && e.uuid == updatedFormula.uuid);
2026-03-15 10:34:04 +00:00
if (index != -1) {
widget.elements[index] = updatedFormula;
}
});
},
),
),
);
}
}
void _importSelected() {
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;
}).toList();
if (selectedElements.isEmpty) {
2026-03-19 11:05:10 +00:00
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('No elements selected to import'), backgroundColor: Colors.orange));
2026-03-15 10:34:04 +00:00
return;
}
try {
2026-03-19 10:25:59 +00:00
widget.corpus.loadFormulaElements(selectedElements, true);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Imported ${selectedElements.length} element(s) successfully'),
backgroundColor: Colors.green,
),
);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
Navigator.pop(context, true);
} catch (e) {
2026-03-19 11:05:10 +00:00
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error importing: $e'), backgroundColor: Colors.red));
2026-03-15 10:34:04 +00:00
}
}
@override
Widget build(BuildContext context) {
final formulas = widget.elements.whereType<Formula>().toList();
final units = widget.elements.whereType<UnitSpec>().toList();
return Scaffold(
appBar: AppBar(
title: const Text('Import Preview'),
2026-03-19 11:05:10 +00:00
actions: [IconButton(icon: const Icon(Icons.check), tooltip: 'Import Selected', onPressed: _importSelected)],
2026-03-15 10:34:04 +00:00
),
body: Column(
children: [
if (formulas.isEmpty && units.isEmpty)
const Padding(
padding: EdgeInsets.all(16.0),
2026-03-19 11:05:10 +00:00
child: Text('No formula elements found in the shared content', style: TextStyle(fontSize: 16)),
2026-03-15 10:34:04 +00:00
)
else
Expanded(
child: ListView(
children: [
if (formulas.isNotEmpty) ...[
const ListTile(
2026-03-19 11:05:10 +00:00
title: Text('Formulas', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
2026-03-15 10:34:04 +00:00
),
...formulas.map((formula) => _buildFormulaTile(formula)),
],
if (units.isNotEmpty) ...[
const ListTile(
2026-03-19 11:05:10 +00:00
title: Text('Units', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
2026-03-15 10:34:04 +00:00
),
...units.map((unit) => _buildUnitTile(unit)),
],
],
),
),
],
),
);
}
Widget _buildFormulaTile(Formula formula) {
final isSelected = _selectedUuids.contains(formula.uuid);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
return Card(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: ListTile(
leading: Checkbox(
value: isSelected,
onChanged: (value) {
setState(() {
if (value == true) {
_selectedUuids.add(formula.uuid);
} else {
_selectedUuids.remove(formula.uuid);
}
});
},
),
title: Text(formula.name),
subtitle: Text(
2026-03-19 11:05:10 +00:00
formula.description?.isNotEmpty == true ? formula.description!.split('\n').first : 'No description',
2026-03-15 10:34:04 +00:00
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (formula.tags.isNotEmpty)
2026-03-19 11:05:10 +00:00
SingleChildScrollView(
child: SizedBox(
width: 150,
child: Wrap(
spacing: 4,
children: formula.tags.take(10).map((tag) {
return Chip(
label: Text(tag, style: const TextStyle(fontSize: 10)),
padding: EdgeInsets.zero,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
);
}).toList(),
),
),
2026-03-15 10:34:04 +00:00
),
2026-03-19 11:05:10 +00:00
IconButton(icon: const Icon(Icons.edit), tooltip: 'Edit', onPressed: () => _editFormulaElement(formula)),
2026-03-15 10:34:04 +00:00
],
),
),
);
}
Widget _buildUnitTile(UnitSpec unit) {
final isSelected = _selectedUuids.contains(unit.name);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
return Card(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: ListTile(
leading: Checkbox(
value: isSelected,
onChanged: (value) {
setState(() {
if (value == true) {
_selectedUuids.add(unit.name);
} else {
_selectedUuids.remove(unit.name);
}
});
},
),
title: Text(unit.name),
subtitle: Text('Base: ${unit.baseUnit} • Symbol: ${unit.symbol}'),
),
);
}
}
/// Screen to import formula elements from text
class ImportFromTextScreen extends StatefulWidget {
final Corpus corpus;
2026-03-19 11:05:10 +00:00
const ImportFromTextScreen({super.key, required this.corpus});
2026-03-15 10:34:04 +00:00
@override
State<ImportFromTextScreen> createState() => _ImportFromTextScreenState();
}
class _ImportFromTextScreenState extends State<ImportFromTextScreen> {
2026-03-19 11:05:10 +00:00
final CodeController _codeController = CodeController(language: dart, text: "// Insert code here...");
2026-03-15 10:34:04 +00:00
bool _isLoading = false;
@override
void dispose() {
_codeController.dispose();
2026-03-15 10:34:04 +00:00
super.dispose();
}
Future<void> _pasteFromClipboard() async {
setState(() => _isLoading = true);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
try {
final clipboardData = await Clipboard.getData('text/plain');
if (clipboardData?.text != null) {
_codeController.text = clipboardData!.text!;
2026-03-15 10:34:04 +00:00
} else {
2026-03-19 11:05:10 +00:00
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Clipboard is empty'), backgroundColor: Colors.orange));
2026-03-15 10:34:04 +00:00
}
} catch (e) {
2026-03-19 11:05:10 +00:00
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error pasting from clipboard: $e'), backgroundColor: Colors.red));
2026-03-15 10:34:04 +00:00
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _import() async {
final text = _codeController.fullText;
2026-03-15 10:34:04 +00:00
if (text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
2026-03-19 11:05:10 +00:00
const SnackBar(content: Text('Please enter or paste formula text'), backgroundColor: Colors.orange),
2026-03-15 10:34:04 +00:00
);
return;
}
setState(() => _isLoading = true);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
try {
final importService = ImportService();
final elements = importService.parseSharedText(text);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
if (!mounted) return;
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
final result = await Navigator.push(
context,
MaterialPageRoute(
2026-03-19 11:05:10 +00:00
builder: (context) => ImportPreviewScreen(elements: elements, corpus: widget.corpus),
2026-03-15 10:34:04 +00:00
),
);
2026-03-19 11:05:10 +00:00
2026-03-15 10:34:04 +00:00
if (result == true) {
Navigator.pop(context, true);
}
} catch (e) {
2026-03-19 11:05:10 +00:00
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error parsing text: $e'), backgroundColor: Colors.red));
2026-03-15 10:34:04 +00:00
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
2026-03-19 11:05:10 +00:00
appBar: AppBar(title: const Text('Import from Text')),
2026-03-15 10:34:04 +00:00
body: Column(
children: [
Expanded(
child: CodeTheme(
data: CodeThemeData(styles: monokaiSublimeTheme),
child: Padding(
padding: const EdgeInsets.all(16.0),
2026-03-19 11:05:10 +00:00
child: SingleChildScrollView(child: CodeField(controller: _codeController)),
2026-03-15 10:34:04 +00:00
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _pasteFromClipboard,
icon: _isLoading
2026-03-19 11:05:10 +00:00
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
2026-03-15 10:34:04 +00:00
: const Icon(Icons.content_paste),
label: const Text('Paste'),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _import,
2026-03-19 11:05:10 +00:00
icon: const Icon(Icons.library_add),
2026-03-15 10:34:04 +00:00
label: const Text('Import'),
),
),
],
),
),
],
),
);
}
}