add some props an features
This commit is contained in:
@@ -17,6 +17,8 @@ class AnwendungController extends GetxController {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final druckbettController = TextEditingController();
|
||||
final druckController = TextEditingController();
|
||||
final empfohleneDrucktemperaturController = TextEditingController();
|
||||
final empfohlenerHeizbettbereichController = TextEditingController();
|
||||
final bauraumController = TextEditingController();
|
||||
final sonstigesController = TextEditingController();
|
||||
final bauraumGeschlossen = false.obs;
|
||||
@@ -48,6 +50,10 @@ class AnwendungController extends GetxController {
|
||||
anwendungen.assignAll([profile]);
|
||||
druckbettController.text = profile.druckbettTemperatur.toString();
|
||||
druckController.text = profile.druckTemperatur.toString();
|
||||
empfohleneDrucktemperaturController.text =
|
||||
profile.empfohleneDrucktemperatur;
|
||||
empfohlenerHeizbettbereichController.text =
|
||||
profile.empfohlenerHeizbettbereich;
|
||||
bauraumController.text = profile.bauraumTemperatur?.toString() ?? '';
|
||||
sonstigesController.text = profile.sonstiges;
|
||||
bauraumGeschlossen.value = profile.bauraumGeschlossen;
|
||||
@@ -67,6 +73,10 @@ class AnwendungController extends GetxController {
|
||||
erstelltAm: existing?.erstelltAm ?? DateTime.now(),
|
||||
druckbettTemperatur: InputValidators.number(druckbettController.text)!,
|
||||
druckTemperatur: InputValidators.number(druckController.text)!,
|
||||
empfohleneDrucktemperatur: empfohleneDrucktemperaturController.text
|
||||
.trim(),
|
||||
empfohlenerHeizbettbereich: empfohlenerHeizbettbereichController.text
|
||||
.trim(),
|
||||
bauraumGeschlossen: bauraumGeschlossen.value,
|
||||
bauraumTemperatur: InputValidators.number(bauraumController.text),
|
||||
sonstiges: sonstigesController.text.trim(),
|
||||
@@ -94,6 +104,8 @@ class AnwendungController extends GetxController {
|
||||
void onClose() {
|
||||
druckbettController.dispose();
|
||||
druckController.dispose();
|
||||
empfohleneDrucktemperaturController.dispose();
|
||||
empfohlenerHeizbettbereichController.dispose();
|
||||
bauraumController.dispose();
|
||||
sonstigesController.dispose();
|
||||
super.onClose();
|
||||
|
||||
@@ -4,6 +4,8 @@ class AnwendungModel {
|
||||
final DateTime erstelltAm;
|
||||
final double druckbettTemperatur;
|
||||
final double druckTemperatur;
|
||||
final String empfohleneDrucktemperatur;
|
||||
final String empfohlenerHeizbettbereich;
|
||||
final bool bauraumGeschlossen;
|
||||
final double? bauraumTemperatur;
|
||||
final String sonstiges;
|
||||
@@ -15,6 +17,8 @@ class AnwendungModel {
|
||||
required this.druckbettTemperatur,
|
||||
required this.druckTemperatur,
|
||||
required this.bauraumGeschlossen,
|
||||
this.empfohleneDrucktemperatur = '',
|
||||
this.empfohlenerHeizbettbereich = '',
|
||||
this.bauraumTemperatur,
|
||||
this.sonstiges = '',
|
||||
});
|
||||
@@ -25,6 +29,8 @@ class AnwendungModel {
|
||||
'erstelltAm': erstelltAm.toIso8601String(),
|
||||
'druckbettTemperatur': druckbettTemperatur,
|
||||
'druckTemperatur': druckTemperatur,
|
||||
'empfohleneDrucktemperatur': empfohleneDrucktemperatur,
|
||||
'empfohlenerHeizbettbereich': empfohlenerHeizbettbereich,
|
||||
'bauraumGeschlossen': bauraumGeschlossen,
|
||||
'bauraumTemperatur': bauraumTemperatur,
|
||||
'sonstiges': sonstiges,
|
||||
@@ -36,6 +42,10 @@ class AnwendungModel {
|
||||
erstelltAm: DateTime.parse(json['erstelltAm'] as String),
|
||||
druckbettTemperatur: (json['druckbettTemperatur'] as num).toDouble(),
|
||||
druckTemperatur: (json['druckTemperatur'] as num).toDouble(),
|
||||
empfohleneDrucktemperatur:
|
||||
json['empfohleneDrucktemperatur'] as String? ?? '',
|
||||
empfohlenerHeizbettbereich:
|
||||
json['empfohlenerHeizbettbereich'] as String? ?? '',
|
||||
bauraumGeschlossen: json['bauraumGeschlossen'] as bool,
|
||||
bauraumTemperatur: (json['bauraumTemperatur'] as num?)?.toDouble(),
|
||||
sonstiges: json['sonstiges'] as String? ?? '',
|
||||
|
||||
@@ -22,11 +22,30 @@ class AnwendungForm extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
EntryFieldGrid(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: controller.empfohleneDrucktemperaturController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Empfohlene Drucktemperatur von–bis',
|
||||
hintText: 'z. B. 220–260 °C',
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: controller.empfohlenerHeizbettbereichController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Empfohlener Heizbettbereich von–bis',
|
||||
hintText: 'z. B. 0–60 °C',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
EntryFieldGrid(
|
||||
children: [
|
||||
EntryNumberField(
|
||||
controller: controller.druckController,
|
||||
label: 'Drucktemperatur',
|
||||
label: 'Nozzletemperatur',
|
||||
unit: '°C',
|
||||
validator: InputValidators.positive,
|
||||
),
|
||||
|
||||
@@ -13,115 +13,155 @@ class BauteilFilamentAuswahl extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Obx(() {
|
||||
final ids = {
|
||||
...controller.filamente.map((item) => item.filamentId),
|
||||
...controller.selectedFilamente.keys,
|
||||
};
|
||||
if (ids.isEmpty) {
|
||||
final ids = controller.selectedFilamente.keys.toList();
|
||||
final availableFilamente = controller.filamente
|
||||
.where(
|
||||
(item) => !controller.selectedFilamente.containsKey(item.filamentId),
|
||||
)
|
||||
.toList();
|
||||
if (controller.filamente.isEmpty && ids.isEmpty) {
|
||||
return const Text(
|
||||
'Erfasse zuerst ein Filament in der Einkaufsverwaltung.',
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: ids.map((id) {
|
||||
final selected = controller.selectedFilamente.containsKey(id);
|
||||
final profiles = controller.profilesFor(id);
|
||||
final available = controller.verfuegbarGramm(id);
|
||||
final checkbox = CheckboxListTile(
|
||||
key: ValueKey('filament-$id'),
|
||||
title: Text(controller.filamentLabel(id)),
|
||||
subtitle: Text(
|
||||
available == null
|
||||
? 'Startgewicht unbekannt'
|
||||
: '${DisplayFormats.number(available)} g verfügbar',
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (availableFilamente.isNotEmpty) ...[
|
||||
DropdownButtonFormField<String>(
|
||||
key: ValueKey(
|
||||
availableFilamente
|
||||
.map((item) => item.filamentId)
|
||||
.toList()
|
||||
.toString(),
|
||||
),
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(labelText: 'Filament hinzufügen'),
|
||||
hint: const Text('Filament auswählen'),
|
||||
items: availableFilamente
|
||||
.map(
|
||||
(item) => DropdownMenuItem(
|
||||
value: item.filamentId,
|
||||
child: Text(
|
||||
controller.filamentLabel(item.filamentId),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (id) {
|
||||
if (id != null) controller.toggleFilament(id, true);
|
||||
},
|
||||
),
|
||||
value: selected,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) => controller.toggleFilament(id, value ?? false),
|
||||
);
|
||||
final consumption = selected
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: EntryNumberField(
|
||||
controller: controller.verbrauchController(id),
|
||||
label: 'Verbrauch',
|
||||
unit: 'g',
|
||||
validator: InputValidators.positive,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
constraints.maxWidth >= 500 && selected
|
||||
? Row(
|
||||
children: [
|
||||
Expanded(child: checkbox),
|
||||
SizedBox(width: 190, child: consumption),
|
||||
],
|
||||
)
|
||||
: Column(children: [checkbox, if (selected) consumption]),
|
||||
),
|
||||
if (selected && profiles.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: DropdownButtonFormField<String>(
|
||||
key: ValueKey(
|
||||
'profile-$id-${controller.selectedFilamente[id]}',
|
||||
),
|
||||
initialValue:
|
||||
profiles.any(
|
||||
(item) =>
|
||||
item.anwendungId ==
|
||||
controller.selectedFilamente[id],
|
||||
const SizedBox(height: 12),
|
||||
] else if (controller.filamente.isNotEmpty) ...[
|
||||
const Text('Alle Filamente wurden hinzugefügt.'),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (ids.isEmpty)
|
||||
const Text(
|
||||
'Noch keine Filamente hinzugefügt.',
|
||||
style: TextStyle(color: AppTheme.muted),
|
||||
),
|
||||
...ids.map((id) {
|
||||
final profiles = controller.profilesFor(id);
|
||||
final available = controller.verfuegbarGramm(id);
|
||||
final filament = ListTile(
|
||||
key: ValueKey('filament-$id'),
|
||||
title: Text(controller.filamentLabel(id)),
|
||||
subtitle: Text(
|
||||
available == null
|
||||
? 'Startgewicht unbekannt'
|
||||
: '${DisplayFormats.number(available)} g verfügbar',
|
||||
),
|
||||
trailing: IconButton(
|
||||
tooltip: 'Filament entfernen',
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => controller.toggleFilament(id, false),
|
||||
),
|
||||
);
|
||||
final consumption = Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: EntryNumberField(
|
||||
controller: controller.verbrauchController(id),
|
||||
label: 'Verbrauch',
|
||||
unit: 'g',
|
||||
validator: InputValidators.positive,
|
||||
),
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) =>
|
||||
constraints.maxWidth >= 500
|
||||
? Row(
|
||||
children: [
|
||||
Expanded(child: filament),
|
||||
SizedBox(width: 190, child: consumption),
|
||||
],
|
||||
)
|
||||
? controller.selectedFilamente[id]
|
||||
: '',
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Verarbeitungsprofil',
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem(
|
||||
value: '',
|
||||
child: Text('Ohne Profil'),
|
||||
: Column(children: [filament, consumption]),
|
||||
),
|
||||
if (profiles.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: DropdownButtonFormField<String>(
|
||||
key: ValueKey(
|
||||
'profile-$id-${controller.selectedFilamente[id]}',
|
||||
),
|
||||
...profiles.map(
|
||||
(profile) => DropdownMenuItem(
|
||||
value: profile.anwendungId,
|
||||
child: Text(
|
||||
'${DisplayFormats.number(profile.druckTemperatur)} °C · Bett ${DisplayFormats.number(profile.druckbettTemperatur)} °C · ${DisplayFormats.date(profile.erstelltAm)}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
initialValue:
|
||||
profiles.any(
|
||||
(item) =>
|
||||
item.anwendungId ==
|
||||
controller.selectedFilamente[id],
|
||||
)
|
||||
? controller.selectedFilamente[id]
|
||||
: '',
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Verarbeitungsprofil',
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem(
|
||||
value: '',
|
||||
child: Text('Ohne Profil'),
|
||||
),
|
||||
...profiles.map(
|
||||
(profile) => DropdownMenuItem(
|
||||
value: profile.anwendungId,
|
||||
child: Text(
|
||||
'${DisplayFormats.number(profile.druckTemperatur)} °C · Bett ${DisplayFormats.number(profile.druckbettTemperatur)} °C · ${DisplayFormats.date(profile.erstelltAm)}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) => controller.selectProfile(
|
||||
id,
|
||||
value == '' ? null : value,
|
||||
),
|
||||
],
|
||||
onChanged: (value) => controller.selectProfile(
|
||||
id,
|
||||
value == '' ? null : value,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (selected && profiles.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: Text(
|
||||
'Noch kein Profil. Einstellungen kannst du über das Filament in der Einkaufsübersicht erfassen.',
|
||||
style: TextStyle(color: AppTheme.muted),
|
||||
if (profiles.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: Text(
|
||||
'Noch kein Profil. Einstellungen kannst du über das Filament in der Einkaufsübersicht erfassen.',
|
||||
style: TextStyle(color: AppTheme.muted),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.2+3
|
||||
version: 1.0.4+5
|
||||
|
||||
environment:
|
||||
sdk: ^3.13.0
|
||||
|
||||
@@ -107,6 +107,8 @@ void main() {
|
||||
final controller = Get.put(AnwendungController(filamentId: 'a'));
|
||||
await show(tester, const AnwendungView(), narrow: true);
|
||||
controller.druckController.text = '220,5';
|
||||
controller.empfohleneDrucktemperaturController.text = '220–260 °C';
|
||||
controller.empfohlenerHeizbettbereichController.text = '0–60 °C';
|
||||
controller.druckbettController.text = '65';
|
||||
controller.bauraumController.text = '40';
|
||||
controller.bauraumGeschlossen.value = true;
|
||||
@@ -120,6 +122,8 @@ void main() {
|
||||
expect(saved.anwendungId, matches(uuidPattern));
|
||||
expect(saved.filamentId, 'a');
|
||||
expect(saved.druckTemperatur, 220.5);
|
||||
expect(saved.empfohleneDrucktemperatur, '220–260 °C');
|
||||
expect(saved.empfohlenerHeizbettbereich, '0–60 °C');
|
||||
expect(saved.druckbettTemperatur, 65);
|
||||
expect(saved.bauraumGeschlossen, isTrue);
|
||||
expect(saved.bauraumTemperatur, 40);
|
||||
@@ -131,6 +135,8 @@ void main() {
|
||||
);
|
||||
expect(reloaded.anwendungen.single.anwendungId, saved.anwendungId);
|
||||
expect(reloaded.druckController.text, '220.5');
|
||||
expect(reloaded.empfohleneDrucktemperaturController.text, '220–260 °C');
|
||||
expect(reloaded.empfohlenerHeizbettbereichController.text, '0–60 °C');
|
||||
expect(reloaded.druckbettController.text, '65.0');
|
||||
expect(reloaded.bauraumController.text, '40.0');
|
||||
expect(reloaded.bauraumGeschlossen.value, isTrue);
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:ertl_filament_verwaltung/services/bauteil_photo_service.dart';
|
||||
import 'package:ertl_filament_verwaltung/services/get_storage_services.dart';
|
||||
import 'package:ertl_filament_verwaltung/theme/app_theme.dart';
|
||||
import 'package:ertl_filament_verwaltung/widgets/bauteil_card.dart';
|
||||
import 'package:ertl_filament_verwaltung/widgets/bauteil_filament_auswahl.dart';
|
||||
import 'package:ertl_filament_verwaltung/widgets/bauteil_photo.dart';
|
||||
|
||||
import 'helpers/memory_storage_service.dart';
|
||||
@@ -123,6 +124,53 @@ void main() {
|
||||
home.filamente.firstWhere((item) => item.filamentId == id),
|
||||
)!;
|
||||
|
||||
testWidgets('Dropdown adds only selected filaments and supports removal', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
GetMaterialApp(
|
||||
theme: AppTheme.light,
|
||||
home: Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: BauteilFilamentAuswahl(controller: controller),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.byKey(const ValueKey('filament-a')), findsNothing);
|
||||
expect(find.byKey(const ValueKey('filament-b')), findsNothing);
|
||||
|
||||
Future<void> add(String id) async {
|
||||
await tester.tap(find.byType(DropdownButtonFormField<String>));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(controller.filamentLabel(id)).last);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
await add('a');
|
||||
controller.verbrauchController('a').text = '25';
|
||||
expect(find.byKey(const ValueKey('filament-a')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('filament-b')), findsNothing);
|
||||
expect(controller.selectedFilamente.keys, ['a']);
|
||||
|
||||
await add('b');
|
||||
expect(find.byType(DropdownButtonFormField<String>), findsNothing);
|
||||
expect(find.byKey(const ValueKey('filament-b')), findsOneWidget);
|
||||
await tester.tap(find.byTooltip('Filament entfernen').first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('filament-a')), findsNothing);
|
||||
expect(controller.selectedFilamente.keys, ['b']);
|
||||
|
||||
await add('a');
|
||||
expect(controller.verbrauchController('a').text, '25');
|
||||
controller.resetForm();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('filament-a')), findsNothing);
|
||||
expect(find.byKey(const ValueKey('filament-b')), findsNothing);
|
||||
expect(find.byType(DropdownButtonFormField<String>), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'Legacy records retain unknown usage and old weight units are supported',
|
||||
() {
|
||||
|
||||
Reference in New Issue
Block a user