Merge branch 'feature/import-formulas'
This commit is contained in:
commit
a7178e2e81
16 changed files with 667 additions and 257 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -18,3 +18,6 @@
|
|||
.aider*
|
||||
.build-container-cache
|
||||
/coverage/
|
||||
/.agent-shell/
|
||||
/ios/
|
||||
/macos/
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
- `flutter pub get` --> `./flutterw pub get`
|
||||
- `flutter run -d linux` --> `./flutterw run -d linux`
|
||||
- See `./Makefile` for more examples.
|
||||
- If you are an agent, you may be also containerized. Try `distrobox-host-exec $(pwd)/flutterw`
|
||||
|
||||
|
||||
# MANDATORY WORKFLOW
|
||||
|
|
|
|||
26
Makefile
26
Makefile
|
|
@ -1,43 +1,45 @@
|
|||
|
||||
all: build-container clean-container build-builders build-linux-debug-container
|
||||
|
||||
DB=~/.local/share/com.example.d4rt_formulas/d4rt_formulas/formulas.sqlite
|
||||
DATABASEFILE=~/.local/share/com.example.d4rt_formulas/d4rt_formulas/formulas.sqlite
|
||||
|
||||
FLUTTERW := $(shell if [ "$$CONTAINER_ID" = "" ]; then echo "./flutterw"; else echo "distrobox-host-exec $(CURDIR)/flutterw"; fi)
|
||||
|
||||
build-container:
|
||||
./flutterw --build-container
|
||||
$(FLUTTERW) --build-container
|
||||
|
||||
clean:
|
||||
flutter clean
|
||||
[ -f $(DB) ] && rm $(DB)
|
||||
[ -f $(DATABASEFILE) ] && rm $(DATABASEFILE)
|
||||
|
||||
clean-container:
|
||||
rm -r .build-container-cache
|
||||
./flutterw clean
|
||||
$(FLUTTERW) clean
|
||||
|
||||
|
||||
pub-get-container:
|
||||
./flutterw pub get
|
||||
$(FLUTTERW) pub get
|
||||
|
||||
test:
|
||||
./flutterw test
|
||||
$(FLUTTERW) test
|
||||
|
||||
build-builders:
|
||||
./flutterw pub run build_runner build --delete-conflicting-outputs
|
||||
$(FLUTTERW) pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
build-android-release-container:
|
||||
./flutterw build apk --release
|
||||
$(FLUTTERW) build apk --release
|
||||
|
||||
build-linux-debug-container:
|
||||
./flutterw build linux --debug
|
||||
$(FLUTTERW) build linux --debug
|
||||
|
||||
build-web-debug-container:
|
||||
./flutterw build web --debug
|
||||
$(FLUTTERW) build web --debug
|
||||
|
||||
run-linux-debug-container:
|
||||
./flutterw run -d linux
|
||||
$(FLUTTERW) run -d linux
|
||||
|
||||
run-web-debug-container:
|
||||
./flutterw run --web-port $${WEB_PORT:-8081} -d web-server
|
||||
$(FLUTTERW) run --web-port $${WEB_PORT:-8081} -d web-server
|
||||
|
||||
run-linux-debug-native:
|
||||
flutter run -d linux
|
||||
|
|
|
|||
11
TODO.md
11
TODO.md
|
|
@ -67,17 +67,18 @@
|
|||
- [R] When a formula is derived in FormulaScreen, the new FormulaScreen is not pushed in navigator, it replacles the current FormulaScreen
|
||||
- [R] In FormulaScreen, a Formula cant be derived if DerivedFormula.isDerivable() returns false
|
||||
- [R] The algorithm of formulaSolver should be https://en.wikipedia.org/wiki/Newton%27s_method
|
||||
- [ ] Use receive_sharing_intent package to implement import of files in linux and android.
|
||||
- [R] Use receive_sharing_intent package to implement import of files in linux and android.
|
||||
- The application will accept *.d4rtf files with the same format of files in ./assets .
|
||||
- The application will accept also shared text, with same format as files in ./assets.
|
||||
- The loaded formulaelemets will be added to the GetIt.instance.get<Corpus)()
|
||||
- [ ] Preview of imported formulalements
|
||||
- The loaded formulaelemets will be added to the GetIt.instance.get<Corpus>()
|
||||
- [R] Preview of imported formulalements
|
||||
- The screen will receive a list of FormulaElements to import
|
||||
- The formulas will have a "edit" button to show a FormulaEditor with the formula
|
||||
- The screen will have an "import all" button to import all the FormulaElements in the list. This will call Corpus.addFormulaElement() for each element, and then pop the screen.
|
||||
- [ ] In FormulaList, add a button next to "export" to import FormulaElements.
|
||||
- [R] In FormulaList, add a button next to "export" to import FormulaElements.
|
||||
- It will show a screen with a text editor with dart syntax and a button "paste".
|
||||
- The "paste" button will copy the clipboard into the text editor.
|
||||
- A second button "import" will use the import preview screen
|
||||
- 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.
|
||||
- [ ] Add a uuid column to the table or FormulaElements, so it is not necessary to load all the formulas to find a formula by uuid. This will improve performance when updating and deleting.
|
||||
- [ ] 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.
|
||||
- [ ] When importing FormulaElements, save the FormulaElements in the database (currently, they are only added to the Corpus in memory).
|
||||
|
|
|
|||
29
android/d4rt_formulas_android.iml
Normal file
29
android/d4rt_formulas_android.iml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
|
||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/app/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/app/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/app/src/main/assets" />
|
||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/app/src/main/libs" />
|
||||
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/app/src/main/proguard_logs" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/app/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/app/src/main/kotlin" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Flutter for Android" level="project" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -9,6 +9,9 @@ import '../database/database_service.dart';
|
|||
import '../service_locator.dart';
|
||||
import 'formula_screen.dart';
|
||||
import 'unit_dropdown.dart';
|
||||
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||
import 'package:flutter_highlight/themes/monokai-sublime.dart';
|
||||
import 'package:highlight/languages/dart.dart';
|
||||
|
||||
/// A screen for editing a Formula's properties including name, description,
|
||||
/// input/output variables, and d4rt code.
|
||||
|
|
@ -17,12 +20,7 @@ class FormulaEditor extends StatefulWidget {
|
|||
final Corpus corpus;
|
||||
final Function(Formula)? onSave;
|
||||
|
||||
const FormulaEditor({
|
||||
super.key,
|
||||
required this.formula,
|
||||
required this.corpus,
|
||||
this.onSave,
|
||||
});
|
||||
const FormulaEditor({super.key, required this.formula, required this.corpus, this.onSave});
|
||||
|
||||
@override
|
||||
State<FormulaEditor> createState() => _FormulaEditorState();
|
||||
|
|
@ -32,7 +30,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController _nameController;
|
||||
late TextEditingController _descriptionController;
|
||||
late TextEditingController _d4rtCodeController;
|
||||
late CodeController _d4rtCodeController;
|
||||
|
||||
// Track input variables
|
||||
final List<_InputVariableRowData> _inputVariables = [];
|
||||
|
|
@ -47,15 +45,17 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.formula.name);
|
||||
_descriptionController = TextEditingController(text: widget.formula.description ?? '');
|
||||
_d4rtCodeController = TextEditingController(text: widget.formula.d4rtCode);
|
||||
_d4rtCodeController = CodeController(language: dart, text: widget.formula.d4rtCode ?? '');
|
||||
|
||||
// Initialize input variables
|
||||
for (final input in widget.formula.input) {
|
||||
_inputVariables.add(_InputVariableRowData(
|
||||
_inputVariables.add(
|
||||
_InputVariableRowData(
|
||||
nameController: TextEditingController(text: input.name),
|
||||
unit: input.unit,
|
||||
values: input.values != null ? List.from(input.values!) : null,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize output variable
|
||||
|
|
@ -79,11 +79,13 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
|
||||
void _addInputVariable() {
|
||||
setState(() {
|
||||
_inputVariables.add(_InputVariableRowData(
|
||||
_inputVariables.add(
|
||||
_InputVariableRowData(
|
||||
nameController: TextEditingController(text: 'var${_inputVariables.length + 1}'),
|
||||
unit: null,
|
||||
values: null,
|
||||
));
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +119,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FormulaScreen(
|
||||
formula: formula,
|
||||
corpus: widget.corpus,
|
||||
),
|
||||
builder: (context) => FormulaScreen(formula: formula, corpus: widget.corpus),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -159,17 +158,12 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
try {
|
||||
final input = <VariableSpec>[];
|
||||
for (final variable in _inputVariables) {
|
||||
input.add(VariableSpec(
|
||||
name: variable.nameController.text.trim(),
|
||||
unit: variable.unit,
|
||||
values: variable.values,
|
||||
));
|
||||
input.add(
|
||||
VariableSpec(name: variable.nameController.text.trim(), unit: variable.unit, values: variable.values),
|
||||
);
|
||||
}
|
||||
|
||||
final output = VariableSpec(
|
||||
name: _outputVariable.nameController.text.trim(),
|
||||
unit: _outputVariable.unit,
|
||||
);
|
||||
final output = VariableSpec(name: _outputVariable.nameController.text.trim(), unit: _outputVariable.unit);
|
||||
|
||||
return Formula(
|
||||
uuid: widget.formula.uuid,
|
||||
|
|
@ -177,7 +171,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
description: _descriptionController.text.isEmpty ? null : _descriptionController.text,
|
||||
input: input,
|
||||
output: output,
|
||||
d4rtCode: _d4rtCodeController.text,
|
||||
d4rtCode: _d4rtCodeController.fullText,
|
||||
tags: widget.formula.tags, // Preserve existing tags
|
||||
);
|
||||
} catch (e) {
|
||||
|
|
@ -296,21 +290,9 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
appBar: AppBar(
|
||||
title: const Text('Edit Formula'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: _testFormula,
|
||||
tooltip: 'Test Formula',
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy),
|
||||
onPressed: _saveFormulaAsCopy,
|
||||
tooltip: 'Save as copy',
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: _saveFormula,
|
||||
tooltip: 'Save',
|
||||
),
|
||||
IconButton(icon: const Icon(Icons.play_arrow), onPressed: _testFormula, tooltip: 'Test Formula'),
|
||||
IconButton(icon: const Icon(Icons.copy), onPressed: _saveFormulaAsCopy, tooltip: 'Save as copy'),
|
||||
IconButton(icon: const Icon(Icons.save), onPressed: _saveFormula, tooltip: 'Save'),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
|
|
@ -363,13 +345,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Description (Markdown)',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Text('Description (Markdown)', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
@ -400,13 +376,8 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
child: Markdown(
|
||||
data: _descriptionController.text,
|
||||
shrinkWrap: true,
|
||||
builders: {
|
||||
'latex': LatexElementBuilder(),
|
||||
},
|
||||
extensionSet: markdown.ExtensionSet(
|
||||
[LatexBlockSyntax()],
|
||||
[LatexInlineSyntax()],
|
||||
),
|
||||
builders: {'latex': LatexElementBuilder()},
|
||||
extensionSet: markdown.ExtensionSet([LatexBlockSyntax()], [LatexInlineSyntax()]),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
@ -432,13 +403,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Input Variables',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Text('Input Variables', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
..._inputVariables.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
|
|
@ -470,10 +435,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
flex: 2,
|
||||
child: TextFormField(
|
||||
controller: variable.nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
decoration: const InputDecoration(labelText: 'Name', border: OutlineInputBorder()),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
|
@ -530,7 +492,8 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
final unitSpec = widget.corpus.getUnit(unit);
|
||||
return DropdownMenuItem<String?>(
|
||||
value: unit,
|
||||
child: Text('${unitSpec.symbol} - ${unit}',
|
||||
child: Text(
|
||||
'${unitSpec.symbol} - ${unit}',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -567,13 +530,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Output Variable',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Text('Output Variable', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
|
|
@ -581,10 +538,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
flex: 2,
|
||||
child: TextFormField(
|
||||
controller: _outputVariable.nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
decoration: const InputDecoration(labelText: 'Name', border: OutlineInputBorder()),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
|
@ -641,7 +595,8 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
final unitSpec = widget.corpus.getUnit(unit);
|
||||
return DropdownMenuItem<String?>(
|
||||
value: unit,
|
||||
child: Text('${unitSpec.symbol} - ${unit}',
|
||||
child: Text(
|
||||
'${unitSpec.symbol} - ${unit}',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -667,67 +622,18 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
|
||||
Widget _buildD4rtCodeSection() {
|
||||
return Card(
|
||||
child: Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'D4RT Code',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(styles: monokaiSublimeTheme),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SingleChildScrollView(
|
||||
child: CodeField(controller: _d4rtCodeController),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Theme.of(context).dividerColor),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
topRight: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.code, size: 16, color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Dart Syntax',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _d4rtCodeController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter D4RT/Dart code here',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
),
|
||||
maxLines: 10,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -763,19 +669,12 @@ class _InputVariableRowData {
|
|||
String? unit;
|
||||
List<dynamic>? values;
|
||||
|
||||
_InputVariableRowData({
|
||||
required this.nameController,
|
||||
this.unit,
|
||||
this.values,
|
||||
});
|
||||
_InputVariableRowData({required this.nameController, this.unit, this.values});
|
||||
}
|
||||
|
||||
class _OutputVariableRowData {
|
||||
final TextEditingController nameController;
|
||||
String? unit;
|
||||
|
||||
_OutputVariableRowData({
|
||||
required this.nameController,
|
||||
this.unit,
|
||||
});
|
||||
_OutputVariableRowData({required this.nameController, this.unit});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,17 @@ import 'formula_screen.dart';
|
|||
import 'package:share_plus/share_plus.dart' as share_plus;
|
||||
import 'formula_editor.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'import_preview_screen.dart';
|
||||
import '../services/import_service.dart';
|
||||
|
||||
class FormulaList extends StatefulWidget {
|
||||
final Corpus corpus;
|
||||
final VoidCallback? onImport;
|
||||
|
||||
const FormulaList({
|
||||
super.key,
|
||||
required this.corpus,
|
||||
this.onImport,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -77,23 +81,6 @@ class _FormulaListState extends State<FormulaList> {
|
|||
}
|
||||
}
|
||||
|
||||
void _editFormula(Formula formula) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FormulaEditor(
|
||||
formula: formula,
|
||||
corpus: widget.corpus,
|
||||
onSave: (updatedFormula){
|
||||
setState((){
|
||||
// THIS UPDATES THE FORMULA LIST
|
||||
});
|
||||
}
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _copyFormula(Formula formula) async {
|
||||
try {
|
||||
final exportString = _formulaAndDependenciesToExportStringLiteral(formula);
|
||||
|
|
@ -133,6 +120,7 @@ class _FormulaListState extends State<FormulaList> {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
|
|
@ -162,13 +150,9 @@ class _FormulaListState extends State<FormulaList> {
|
|||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () => _editFormula(formula),
|
||||
tooltip: 'Edit Formula',
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.share),
|
||||
tooltip: 'Share or copy to clipboard',
|
||||
onSelected: (value) {
|
||||
if (value == 'share') {
|
||||
_shareFormula(formula);
|
||||
|
|
|
|||
|
|
@ -121,14 +121,13 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
}
|
||||
|
||||
late final dynamic result;
|
||||
//if( formula is DerivedFormula) {
|
||||
if( formula is DerivedFormula) {
|
||||
result = formulaSolver(formula, formula.output.name, inputValues,);
|
||||
//}
|
||||
//else {
|
||||
// TODO: MAYBE ONLY FORMULASOLVER IS NECCESSARY"
|
||||
//final evaluator = FormulaEvaluator();
|
||||
//result = evaluator.evaluate(formula as Formula, inputValues);
|
||||
//}
|
||||
}
|
||||
else {
|
||||
final evaluator = FormulaEvaluator();
|
||||
result = evaluator.evaluate(formula as Formula, inputValues);
|
||||
}
|
||||
|
||||
// Convert output to selected unit if needed
|
||||
String? unit = formula.output.unit;
|
||||
|
|
@ -342,7 +341,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
children: [
|
||||
// Fixed width for field name
|
||||
SizedBox(
|
||||
width: 150,
|
||||
width: 50,
|
||||
child: Text(
|
||||
formula.output.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
|
@ -353,7 +352,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
Expanded(
|
||||
child: TextFormField(
|
||||
readOnly: true,
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
controller: TextEditingController(text: _result),
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
|
|
@ -388,10 +387,10 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
children: [
|
||||
// Fixed width for field name
|
||||
SizedBox(
|
||||
width: 150,
|
||||
width: 50,
|
||||
child: Text(
|
||||
variable.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
overflow: TextOverflow.fade
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8), // Add some spacing
|
||||
|
|
|
|||
140
lib/ai/import_from_text_screen.dart
Normal file
140
lib/ai/import_from_text_screen.dart
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import 'package:d4rt_formulas/d4rt_formulas.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:flutter_highlight/themes/monokai-sublime.dart';
|
||||
import 'package:highlight/languages/dart.dart';
|
||||
|
||||
import '../database/database_service.dart';
|
||||
import '../service_locator.dart';
|
||||
|
||||
import '../services/import_service.dart';
|
||||
import 'formula_list.dart';
|
||||
import '../corpus.dart';
|
||||
import '../defaults/default_corpus.dart';
|
||||
import '../formula_models.dart' as models;
|
||||
import 'import_preview_screen.dart';
|
||||
|
||||
|
||||
/// Screen to import formula elements from text
|
||||
class ImportFromTextScreen extends StatefulWidget {
|
||||
final Corpus corpus;
|
||||
|
||||
const ImportFromTextScreen({super.key, required this.corpus});
|
||||
|
||||
@override
|
||||
State<ImportFromTextScreen> createState() => _ImportFromTextScreenState();
|
||||
}
|
||||
|
||||
class _ImportFromTextScreenState extends State<ImportFromTextScreen> {
|
||||
final CodeController _codeController = CodeController(language: dart, text: "// Insert code here...");
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_codeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _pasteFromClipboard() async {
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final clipboardData = await Clipboard.getData('text/plain');
|
||||
if (clipboardData?.text != null) {
|
||||
_codeController.text = clipboardData!.text!;
|
||||
} else {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Clipboard is empty'), backgroundColor: Colors.orange));
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error pasting from clipboard: $e'), backgroundColor: Colors.red));
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _import() async {
|
||||
final text = _codeController.fullText;
|
||||
if (text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Please enter or paste formula text'), backgroundColor: Colors.orange),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final importService = ImportService();
|
||||
final elements = importService.parseSharedText(text);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
final result = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ImportPreviewScreen(elements: elements, corpus: widget.corpus),
|
||||
),
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error parsing text: $e'), backgroundColor: Colors.red));
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Import from Text')),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(styles: monokaiSublimeTheme),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SingleChildScrollView(child: CodeField(controller: _codeController)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading ? null : _pasteFromClipboard,
|
||||
icon: _isLoading
|
||||
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: const Icon(Icons.content_paste),
|
||||
label: const Text('Paste'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _isLoading ? null : _import,
|
||||
icon: const Icon(Icons.library_add),
|
||||
label: const Text('Import'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
212
lib/ai/import_preview_screen.dart
Normal file
212
lib/ai/import_preview_screen.dart
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
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';
|
||||
import 'package:highlight/languages/dart.dart';
|
||||
|
||||
/// Screen to preview and import formula elements
|
||||
class ImportPreviewScreen extends StatefulWidget {
|
||||
final List<FormulaElement> elements;
|
||||
final Corpus corpus;
|
||||
|
||||
const ImportPreviewScreen({super.key, required this.elements, required this.corpus});
|
||||
|
||||
@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(() {
|
||||
final index = widget.elements.indexWhere((e) => e is Formula && e.uuid == updatedFormula.uuid);
|
||||
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) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('No elements selected to import'), backgroundColor: Colors.orange));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
widget.corpus.loadFormulaElements(selectedElements, true);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Imported ${selectedElements.length} element(s) successfully'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
Navigator.pop(context, true);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error importing: $e'), backgroundColor: Colors.red));
|
||||
}
|
||||
}
|
||||
|
||||
@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'),
|
||||
actions: [IconButton(icon: const Icon(Icons.check), tooltip: 'Import Selected', onPressed: _importSelected)],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
if (formulas.isEmpty && units.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Text('No formula elements found in the shared content', style: TextStyle(fontSize: 16)),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
if (formulas.isNotEmpty) ...[
|
||||
const ListTile(
|
||||
title: Text('Formulas', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
...formulas.map((formula) => _buildFormulaTile(formula)),
|
||||
],
|
||||
if (units.isNotEmpty) ...[
|
||||
const ListTile(
|
||||
title: Text('Units', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
...units.map((unit) => _buildUnitTile(unit)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFormulaTile(Formula formula) {
|
||||
final isSelected = _selectedUuids.contains(formula.uuid);
|
||||
|
||||
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(
|
||||
formula.description?.isNotEmpty == true ? formula.description!.split('\n').first : 'No description',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (formula.tags.isNotEmpty)
|
||||
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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(icon: const Icon(Icons.edit), tooltip: 'Edit', onPressed: () => _editFormulaElement(formula)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUnitTile(UnitSpec unit) {
|
||||
final isSelected = _selectedUuids.contains(unit.name);
|
||||
|
||||
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}'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -22,15 +22,12 @@ class UnitDropdown extends StatelessWidget {
|
|||
final availableUnits = unitNames.map((name) => corpus.getUnit(name)).toList();
|
||||
|
||||
return SizedBox(
|
||||
width: 200, // Constrain dropdown width
|
||||
width: 50, // Constrain dropdown width
|
||||
child: DropdownButton<String>(
|
||||
value: selectedUnit ?? variable.unit,
|
||||
selectedItemBuilder: (context) => availableUnits.map((unit) =>
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: Text(unit.symbol, overflow: TextOverflow.ellipsis),
|
||||
)
|
||||
).toList(),
|
||||
selectedItemBuilder: (context) => availableUnits
|
||||
.map((unit) => SizedBox(width: 50, child: Text(unit.symbol, overflow: TextOverflow.ellipsis)))
|
||||
.toList(),
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
elevation: 16,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary, fontSize: 14),
|
||||
|
|
@ -40,14 +37,16 @@ class UnitDropdown extends StatelessWidget {
|
|||
return DropdownMenuItem<String>(
|
||||
value: unit.name,
|
||||
child: SizedBox(
|
||||
width: 200, // Fixed width for all items
|
||||
child: Text("${unit.symbol} - ${unit.name}",
|
||||
width: 300, // Fixed width for all items
|
||||
child: Text(
|
||||
"${unit.symbol} - ${unit.name}",
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
menuWidth: 300,
|
||||
menuMaxHeight: 400,
|
||||
isExpanded: true,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ class Corpus{
|
|||
|
||||
/// Loads formula elements, making sure to load units first, then formulas
|
||||
/// to avoid dependency issues.
|
||||
void loadFormulaElements(List<FormulaElement> elements) {
|
||||
void loadFormulaElements(List<FormulaElement> elements, [bool replaceOnDuplicates = false]) {
|
||||
List<UnitSpec> units = [];
|
||||
List<Formula> formulas = [];
|
||||
|
||||
|
|
@ -239,10 +239,10 @@ class Corpus{
|
|||
}
|
||||
|
||||
// Load units first to satisfy dependencies
|
||||
loadUnits(units);
|
||||
loadUnits(units, replaceOnDuplicates);
|
||||
|
||||
// Then load formulas
|
||||
loadFormulas(formulas);
|
||||
loadFormulas(formulas, replaceOnDuplicates: replaceOnDuplicates, checkUnits: true);
|
||||
}
|
||||
|
||||
/// Loads corpus from database elements
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ extension CorpusDatabaseExtension on FormulasDatabase {
|
|||
for (final element in elements) {
|
||||
try {
|
||||
final parsed = SetUtils.parseCorpusElements('[${element.elementText}]');
|
||||
print("PARSED:$element");
|
||||
parsedElements.addAll(parsed);
|
||||
} catch (e) {
|
||||
print('Error parsing database element: $e');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:d4rt_formulas/d4rt_formulas.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'ai/import_from_text_screen.dart';
|
||||
import 'database/database_service.dart';
|
||||
import 'service_locator.dart';
|
||||
|
||||
|
|
@ -18,9 +19,13 @@ void main() async {
|
|||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
final GlobalKey<_CorpusLoaderState> corpusLoaderKey = GlobalKey<_CorpusLoaderState>();
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
get corpusFuture => corpusLoaderKey.currentState?._corpusFuture;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
|
|
@ -30,19 +35,41 @@ class MyApp extends StatelessWidget {
|
|||
}
|
||||
|
||||
class CorpusLoader extends StatefulWidget {
|
||||
CorpusLoader({Key? key}) : super(key: corpusLoaderKey);
|
||||
|
||||
@override
|
||||
_CorpusLoaderState createState() => _CorpusLoaderState();
|
||||
State<CorpusLoader> createState() => _CorpusLoaderState();
|
||||
}
|
||||
|
||||
class _CorpusLoaderState extends State<CorpusLoader> {
|
||||
late Future<Corpus> _corpusFuture;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_corpusFuture = loadCorpusFromDatabaseOrAssets();
|
||||
}
|
||||
|
||||
void _handleImport() {
|
||||
_corpusFuture.then((corpus) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ImportFromTextScreen(
|
||||
corpus: corpus,
|
||||
),
|
||||
),
|
||||
).then((result) {
|
||||
if (result) {
|
||||
setState(() {
|
||||
// Refresh the list when returning from import
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<Corpus>(
|
||||
|
|
@ -59,9 +86,19 @@ class _CorpusLoaderState extends State<CorpusLoader> {
|
|||
// If the corpus is empty (user chose not to load default), we could handle that here
|
||||
// For now, just display the formula list
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Formulas')),
|
||||
appBar: AppBar(
|
||||
title: const Text('Formulas'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.library_add),
|
||||
tooltip: 'Import formulas',
|
||||
onPressed: _handleImport,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: FormulaList(
|
||||
corpus: snapshot.data!,
|
||||
onImport: _handleImport,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
99
lib/services/import_service.dart
Normal file
99
lib/services/import_service.dart
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import 'dart:io';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:d4rt_formulas/formula_models.dart';
|
||||
import 'package:d4rt_formulas/set_utils.dart';
|
||||
import 'package:d4rt_formulas/error_handler.dart';
|
||||
|
||||
/// Service to handle import of formula elements from shared files or text
|
||||
class ImportService {
|
||||
static final ImportService _instance = ImportService._internal();
|
||||
factory ImportService() => _instance;
|
||||
ImportService._internal();
|
||||
|
||||
/// Parses shared text content as formula elements
|
||||
/// The text should be in the same format as files in ./assets/formulas
|
||||
List<FormulaElement> parseSharedText(String text) {
|
||||
try {
|
||||
final List<Object?> list = SetUtils.parseD4rtLiteral(text);
|
||||
|
||||
final elements = <FormulaElement>[];
|
||||
for (final item in list) {
|
||||
if (item is Map) {
|
||||
// Try to parse as Formula first (has 'd4rtCode' field)
|
||||
if (item.containsKey('d4rtCode')) {
|
||||
elements.add(Formula.fromSet(item));
|
||||
}
|
||||
// Try to parse as UnitSpec (has 'name' and 'baseUnit' or 'isBase')
|
||||
else if (item.containsKey('name')) {
|
||||
elements.add(UnitSpec.fromSet(item));
|
||||
}
|
||||
else {
|
||||
throw ArgumentError('Unknown element type: $item');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
} catch (e, stack) {
|
||||
errorHandler.notify(e, stack);
|
||||
throw FormatException('Failed to parse shared text as formula elements: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a .d4rtf file content as formula elements
|
||||
List<FormulaElement> parseD4rtfFile(String filePath) {
|
||||
try {
|
||||
final file = File(filePath);
|
||||
if (!file.existsSync()) {
|
||||
throw FileSystemException('File not found', filePath);
|
||||
}
|
||||
|
||||
final content = file.readAsStringSync();
|
||||
return parseSharedText(content);
|
||||
} catch (e, stack) {
|
||||
errorHandler.notify(e, stack);
|
||||
throw FormatException('Failed to parse .d4rtf file: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Listens for shared files (Android only for now)
|
||||
Stream<List<SharedMediaFile>> get sharedFilesStream {
|
||||
return ReceiveSharingIntent.instance.getMediaStream();
|
||||
}
|
||||
|
||||
/// Gets initial shared media (for when app is launched via share)
|
||||
Future<List<SharedMediaFile>> getInitialSharedMedia() async {
|
||||
try {
|
||||
return await ReceiveSharingIntent.instance.getInitialMedia();
|
||||
} catch (e, stack) {
|
||||
errorHandler.notify(e, stack);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets shared text (for when app receives text via share)
|
||||
Future<String?> getSharedText() async {
|
||||
try {
|
||||
final media = await ReceiveSharingIntent.instance.getInitialMedia();
|
||||
// Note: In newer versions of receive_sharing_intent, TEXT type may not be available
|
||||
// We check if media exists and try to get the path
|
||||
if (media.isNotEmpty) {
|
||||
return media.first.path;
|
||||
}
|
||||
return null;
|
||||
} catch (e, stack) {
|
||||
errorHandler.notify(e, stack);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the initial shared media after processing
|
||||
Future<void> clearInitialSharedMedia() async {
|
||||
try {
|
||||
// Note: resetInitialMedia() was removed in newer versions
|
||||
// The media is automatically cleared after being read
|
||||
} catch (e, stack) {
|
||||
errorHandler.notify(e, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,6 @@ abstract class SetUtils {
|
|||
}
|
||||
|
||||
/// Escapes special characters in a string for use in D4RT literals
|
||||
@deprecated
|
||||
static String escapeD4rtString(String input) {
|
||||
return input
|
||||
.replaceAll(r'\\', r'\\\\') // escape backslashes first
|
||||
|
|
@ -75,9 +74,9 @@ abstract class SetUtils {
|
|||
/// Uses JSON-like formatting but for Dart language, with proper indentation.
|
||||
static String prettyPrint(dynamic value, {int indent = 0}) {
|
||||
if (value is String) {
|
||||
return _prettyPrintString(value, indent);
|
||||
return _prettyPrintString(value);
|
||||
} else if (value is num) {
|
||||
return _prettyPrintNumber(value, indent);
|
||||
return _prettyPrintNumber(value);
|
||||
} else if (value is Set) {
|
||||
return _prettyPrintSet(value, indent);
|
||||
} else if (value is List) {
|
||||
|
|
@ -90,15 +89,15 @@ abstract class SetUtils {
|
|||
}
|
||||
|
||||
/// Pretty prints a simple string, escaping special characters if needed.
|
||||
static String _prettyPrintString(String s, int indent) {
|
||||
static String _prettyPrintString(String s) {
|
||||
// Check if the string needs raw string formatting (newlines, $, backslashes, quotes)
|
||||
final needsRawString = s.contains('\n') ||
|
||||
s.contains(r'$') ||
|
||||
s.contains(r'\\') ||
|
||||
s.contains('"');
|
||||
|
||||
if (needsRawString) {
|
||||
return _prettyPrintRawString(s, indent);
|
||||
if (needsRawString && s != '"' ) {
|
||||
return _prettyPrintRawString(s);
|
||||
}
|
||||
|
||||
// Simple string with escaped quotes
|
||||
|
|
@ -107,7 +106,7 @@ abstract class SetUtils {
|
|||
}
|
||||
|
||||
/// Pretty prints a number.
|
||||
static String _prettyPrintNumber(num n, int indent) {
|
||||
static String _prettyPrintNumber(num n) {
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
|
|
@ -157,9 +156,16 @@ abstract class SetUtils {
|
|||
|
||||
/// Pretty prints a raw string (for strings containing newlines, $, backslashes, etc.)
|
||||
/// Uses Dart's raw string syntax r"""..."""
|
||||
static String _prettyPrintRawString(String s, int indent) {
|
||||
// Escape triple quotes by replacing """ with ""\"
|
||||
final escaped = s.replaceAll('"""', r'""\\"');
|
||||
return 'r"""$escaped"""';
|
||||
static String _prettyPrintRawString(String s) {
|
||||
if( s == '"'){
|
||||
return "'\"";
|
||||
}
|
||||
if( s.contains('"""') && s.contains("'''") ){
|
||||
return escapeD4rtString(s);
|
||||
}
|
||||
if( s.contains('"""') ){
|
||||
return "r'''$s'''";
|
||||
}
|
||||
return 'r"""$s"""';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue