Compare commits

..

5 commits

8 changed files with 206 additions and 30 deletions

View file

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class NewAskForNameDialog extends HookWidget {
final String title;
final String? labelText;
const NewAskForNameDialog({required this.title, this.labelText, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final tec = useTextEditingController();
final onPressed = useMemoized(() => () {
Navigator.of(context).pop(tec.text);
}, [tec.text]);
return Dialog(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerRight,
child: IconButton(
icon: const Icon(Icons.close),
tooltip: 'Close',
onPressed: () {
Navigator.of(context).pop();
},
),
),
Text(
title,
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
constraints: const BoxConstraints(minWidth: 300),
child: TextField(
controller: tec,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: labelText,
suffixIcon: IconButton(
icon: const Icon(Icons.done),
onPressed: onPressed,
),
),
onSubmitted: (_) => onPressed(),
),
),
),
],
),
),
),
);
}
}

View file

@ -20,8 +20,9 @@ class NewProjectDialog extends HookWidget {
};
}, [newDialogNameController.text]);
return Center(
child: Card(
return Dialog(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,

View file

@ -34,7 +34,12 @@ class MyApp extends StatelessWidget {
title: 'Logic Circuits Simulator',
theme: ThemeData(
useMaterial3: true,
primarySwatch: Colors.amber,
primarySwatch: Colors.orange,
),
darkTheme: ThemeData(
useMaterial3: true,
primarySwatch: Colors.orange,
brightness: Brightness.dark,
),
routes: {
ProjectsPage.routeName:(context) {

View file

@ -23,6 +23,8 @@ class ComponentEntry with _$ComponentEntry {
required List<String> outputs,
@JsonKey(includeIfNull: false)
List<String>? truthTable,
@JsonKey(includeIfNull: false)
String? logicExpression,
}) = _ComponentEntry;
factory ComponentEntry.fromJson(Map<String, Object?> json) => _$ComponentEntryFromJson(json);

View file

@ -166,6 +166,8 @@ mixin _$ComponentEntry {
List<String> get outputs => throw _privateConstructorUsedError;
@JsonKey(includeIfNull: false)
List<String>? get truthTable => throw _privateConstructorUsedError;
@JsonKey(includeIfNull: false)
String? get logicExpression => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@ -184,7 +186,8 @@ abstract class $ComponentEntryCopyWith<$Res> {
@JsonKey(includeIfNull: false) String? componentDescription,
List<String> inputs,
List<String> outputs,
@JsonKey(includeIfNull: false) List<String>? truthTable});
@JsonKey(includeIfNull: false) List<String>? truthTable,
@JsonKey(includeIfNull: false) String? logicExpression});
}
/// @nodoc
@ -204,6 +207,7 @@ class _$ComponentEntryCopyWithImpl<$Res>
Object? inputs = freezed,
Object? outputs = freezed,
Object? truthTable = freezed,
Object? logicExpression = freezed,
}) {
return _then(_value.copyWith(
componentId: componentId == freezed
@ -230,6 +234,10 @@ class _$ComponentEntryCopyWithImpl<$Res>
? _value.truthTable
: truthTable // ignore: cast_nullable_to_non_nullable
as List<String>?,
logicExpression: logicExpression == freezed
? _value.logicExpression
: logicExpression // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
@ -247,7 +255,8 @@ abstract class _$$_ComponentEntryCopyWith<$Res>
@JsonKey(includeIfNull: false) String? componentDescription,
List<String> inputs,
List<String> outputs,
@JsonKey(includeIfNull: false) List<String>? truthTable});
@JsonKey(includeIfNull: false) List<String>? truthTable,
@JsonKey(includeIfNull: false) String? logicExpression});
}
/// @nodoc
@ -269,6 +278,7 @@ class __$$_ComponentEntryCopyWithImpl<$Res>
Object? inputs = freezed,
Object? outputs = freezed,
Object? truthTable = freezed,
Object? logicExpression = freezed,
}) {
return _then(_$_ComponentEntry(
componentId: componentId == freezed
@ -295,6 +305,10 @@ class __$$_ComponentEntryCopyWithImpl<$Res>
? _value._truthTable
: truthTable // ignore: cast_nullable_to_non_nullable
as List<String>?,
logicExpression: logicExpression == freezed
? _value.logicExpression
: logicExpression // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
@ -308,7 +322,8 @@ class _$_ComponentEntry implements _ComponentEntry {
@JsonKey(includeIfNull: false) this.componentDescription,
required final List<String> inputs,
required final List<String> outputs,
@JsonKey(includeIfNull: false) final List<String>? truthTable})
@JsonKey(includeIfNull: false) final List<String>? truthTable,
@JsonKey(includeIfNull: false) this.logicExpression})
: _inputs = inputs,
_outputs = outputs,
_truthTable = truthTable;
@ -347,9 +362,13 @@ class _$_ComponentEntry implements _ComponentEntry {
return EqualUnmodifiableListView(value);
}
@override
@JsonKey(includeIfNull: false)
final String? logicExpression;
@override
String toString() {
return 'ComponentEntry(componentId: $componentId, componentName: $componentName, componentDescription: $componentDescription, inputs: $inputs, outputs: $outputs, truthTable: $truthTable)';
return 'ComponentEntry(componentId: $componentId, componentName: $componentName, componentDescription: $componentDescription, inputs: $inputs, outputs: $outputs, truthTable: $truthTable, logicExpression: $logicExpression)';
}
@override
@ -366,7 +385,9 @@ class _$_ComponentEntry implements _ComponentEntry {
const DeepCollectionEquality().equals(other._inputs, _inputs) &&
const DeepCollectionEquality().equals(other._outputs, _outputs) &&
const DeepCollectionEquality()
.equals(other._truthTable, _truthTable));
.equals(other._truthTable, _truthTable) &&
const DeepCollectionEquality()
.equals(other.logicExpression, logicExpression));
}
@JsonKey(ignore: true)
@ -378,7 +399,8 @@ class _$_ComponentEntry implements _ComponentEntry {
const DeepCollectionEquality().hash(componentDescription),
const DeepCollectionEquality().hash(_inputs),
const DeepCollectionEquality().hash(_outputs),
const DeepCollectionEquality().hash(_truthTable));
const DeepCollectionEquality().hash(_truthTable),
const DeepCollectionEquality().hash(logicExpression));
@JsonKey(ignore: true)
@override
@ -398,7 +420,8 @@ abstract class _ComponentEntry implements ComponentEntry {
@JsonKey(includeIfNull: false) final String? componentDescription,
required final List<String> inputs,
required final List<String> outputs,
@JsonKey(includeIfNull: false) final List<String>? truthTable}) =
@JsonKey(includeIfNull: false) final List<String>? truthTable,
@JsonKey(includeIfNull: false) final String? logicExpression}) =
_$_ComponentEntry;
factory _ComponentEntry.fromJson(Map<String, dynamic> json) =
@ -419,6 +442,9 @@ abstract class _ComponentEntry implements ComponentEntry {
@JsonKey(includeIfNull: false)
List<String>? get truthTable => throw _privateConstructorUsedError;
@override
@JsonKey(includeIfNull: false)
String? get logicExpression => throw _privateConstructorUsedError;
@override
@JsonKey(ignore: true)
_$$_ComponentEntryCopyWith<_$_ComponentEntry> get copyWith =>
throw _privateConstructorUsedError;

View file

@ -30,6 +30,7 @@ _$_ComponentEntry _$$_ComponentEntryFromJson(Map<String, dynamic> json) =>
truthTable: (json['truthTable'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
logicExpression: json['logicExpression'] as String?,
);
Map<String, dynamic> _$$_ComponentEntryToJson(_$_ComponentEntry instance) {
@ -48,5 +49,6 @@ Map<String, dynamic> _$$_ComponentEntryToJson(_$_ComponentEntry instance) {
val['inputs'] = instance.inputs;
val['outputs'] = instance.outputs;
writeNotNull('truthTable', instance.truthTable);
writeNotNull('logicExpression', instance.logicExpression);
return val;
}

View file

@ -1,6 +1,9 @@
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:logic_circuits_simulator/dialogs/new_ask_for_name.dart';
import 'package:logic_circuits_simulator/models/project.dart';
import 'package:logic_circuits_simulator/state/project.dart';
import 'package:logic_circuits_simulator/utils/iterable_extension.dart';
@ -18,11 +21,12 @@ class EditComponentPage extends HookWidget {
Widget build(BuildContext context) {
final anySave = useState(false);
final projectState = useProvider<ProjectState>();
final ce = projectState.index.components.where((c) => c.componentId == component.componentId).first;
final truthTable = useState(ce.truthTable?.toList());
final inputs = useState(ce.inputs.toList());
final outputs = useState(ce.outputs.toList());
final componentNameEditingController = useTextEditingController(text: ce.componentName);
ComponentEntry ce() => projectState.index.components.where((c) => c.componentId == component.componentId).first;
final truthTable = useState(ce().truthTable?.toList());
final logicExpression = useState(ce().logicExpression);
final inputs = useState(ce().inputs.toList());
final outputs = useState(ce().outputs.toList());
final componentNameEditingController = useTextEditingController(text: ce().componentName);
useValueListenable(componentNameEditingController);
final dirty = useMemoized(
() {
@ -38,29 +42,37 @@ class EditComponentPage extends HookWidget {
// Don't allow saving empty outputs
return false;
}
if (componentNameEditingController.text != ce.componentName) {
if (truthTable.value == null && logicExpression.value == null) {
// Don't allow saving components without functionality
return false;
}
if (componentNameEditingController.text != ce().componentName) {
return true;
}
if (!const ListEquality().equals(inputs.value, ce.inputs)) {
if (!const ListEquality().equals(inputs.value, ce().inputs)) {
return true;
}
if (!const ListEquality().equals(outputs.value, ce.outputs)) {
if (!const ListEquality().equals(outputs.value, ce().outputs)) {
return true;
}
if (!const ListEquality().equals(truthTable.value, ce.truthTable)) {
if (!const ListEquality().equals(truthTable.value, ce().truthTable)) {
return true;
}
if (logicExpression.value != ce().logicExpression) {
return true;
}
return false;
},
[
componentNameEditingController.text,
ce.componentName,
ce().componentName,
inputs.value,
ce.inputs,
ce().inputs,
outputs.value,
ce.outputs,
ce().outputs,
truthTable.value,
ce.truthTable,
ce().truthTable,
],
);
@ -136,8 +148,20 @@ class EditComponentPage extends HookWidget {
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Add new input',
onPressed: () {
onPressed: () async {
final inputName = await showDialog<String>(
context: context,
builder: (context) {
return const NewAskForNameDialog(
title: 'New Input',
labelText: 'Input name',
);
},
);
if (inputName != null) {
truthTable.value = truthTable.value?.expand((element) => [element, element]).toList();
inputs.value = inputs.value.toList()..add(inputName);
}
},
),
],
@ -216,8 +240,20 @@ class EditComponentPage extends HookWidget {
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Add new output',
onPressed: () {
onPressed: () async {
final outputName = await showDialog<String>(
context: context,
builder: (context) {
return const NewAskForNameDialog(
title: 'New Output',
labelText: 'Output name',
);
},
);
if (outputName != null) {
truthTable.value = truthTable.value?.map((e) => '${e}0').toList();
outputs.value = outputs.value.toList()..add(outputName);
}
},
),
],
@ -271,6 +307,48 @@ class EditComponentPage extends HookWidget {
childCount: outputs.value.length,
),
),
if (truthTable.value == null && logicExpression.value == null) ...[
SliverToBoxAdapter(
child: Column(
children: [
const Divider(),
Text(
'Choose component kind',
style: Theme.of(context).textTheme.headline4,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: OutlinedButton(
onPressed: () {
logicExpression.value = '';
},
child: const Text('Logic Expression'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: OutlinedButton(
onPressed: () {
final row = "0" * outputs.value.length;
truthTable.value = List.generate(pow(2, inputs.value.length) as int, (_) => row);
},
child: const Text('Truth Table'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: OutlinedButton(
onPressed: null,
child: const Text('Visual Designer'),
),
),
],
),
),
],
if (logicExpression.value != null) ...[
],
if (truthTable.value != null) ...[
SliverToBoxAdapter(
child: Padding(
@ -300,9 +378,9 @@ class EditComponentPage extends HookWidget {
floatingActionButton: !dirty ? null : FloatingActionButton(
onPressed: () async {
if (componentNameEditingController.text.isNotEmpty) {
await projectState.editComponent(component.copyWith(componentName: componentNameEditingController.text));
await projectState.editComponent(ce().copyWith(componentName: componentNameEditingController.text));
}
await projectState.editComponent(ce.copyWith(
await projectState.editComponent(ce().copyWith(
inputs: inputs.value,
outputs: outputs.value,
truthTable: truthTable.value,

View file

@ -28,7 +28,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
version: "2.9.0"
boolean_selector:
dependency: transitive
description: