import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '../models/filament_model.dart'; class AddFilamentDialog extends StatefulWidget { final FilamentModel? existingEntry; const AddFilamentDialog({super.key, this.existingEntry}); static Future show() { return Get.dialog( const AddFilamentDialog(), barrierColor: Colors.black.withValues(alpha: 0.55), ); } static Future showEdit({required FilamentModel entry}) { return Get.dialog( AddFilamentDialog(existingEntry: entry), barrierColor: Colors.black.withValues(alpha: 0.55), ); } @override State createState() => _AddFilamentDialogState(); } class _AddFilamentDialogState extends State with SingleTickerProviderStateMixin { final _formKey = GlobalKey(); final _nameCtrl = TextEditingController(); final _colorCtrl = TextEditingController(); final _weightCtrl = TextEditingController(); final _weightUsedCtrl = TextEditingController(); final _priceCtrl = TextEditingController(); final _manufacturerCtrl = TextEditingController(); final _notesCtrl = TextEditingController(); final _printingTempCtrl = TextEditingController(); final _bedTempCtrl = TextEditingController(); String _selectedType = 'PLA'; String _purchaseDate = ''; late AnimationController _anim; late Animation _fadeScale; static const _filamentTypes = [ 'PLA', 'PETG', 'ABS', 'TPU', 'ASA', 'Nylon', 'Resin', 'Sonstiges', ]; @override void initState() { super.initState(); final e = widget.existingEntry; if (e != null) { _nameCtrl.text = e.name; _selectedType = _filamentTypes.contains(e.type) ? e.type : 'Sonstiges'; _colorCtrl.text = e.color; _weightCtrl.text = e.weight.toString(); _weightUsedCtrl.text = e.weightUsed.toString(); _priceCtrl.text = e.price.toString(); _manufacturerCtrl.text = e.manufacturer; _purchaseDate = e.purchaseDate; _notesCtrl.text = e.notes ?? ''; _printingTempCtrl.text = e.printingTemp.toString(); _bedTempCtrl.text = e.bedTemp.toString(); } else { _purchaseDate = DateTime.now().toIso8601String().substring(0, 10); _weightCtrl.text = '1000'; _weightUsedCtrl.text = '0'; _printingTempCtrl.text = '210'; _bedTempCtrl.text = '60'; } _anim = AnimationController( vsync: this, duration: const Duration(milliseconds: 220), ); _fadeScale = CurvedAnimation(parent: _anim, curve: Curves.easeOutBack); _anim.forward(); } @override void dispose() { for (final c in [ _nameCtrl, _colorCtrl, _weightCtrl, _weightUsedCtrl, _priceCtrl, _manufacturerCtrl, _notesCtrl, _printingTempCtrl, _bedTempCtrl, ]) { c.dispose(); } _anim.dispose(); super.dispose(); } Future _pickDate() async { final initial = DateTime.tryParse(_purchaseDate) ?? DateTime.now(); final picked = await showDatePicker( context: context, initialDate: initial, firstDate: DateTime(2000), lastDate: DateTime(2100), builder: (context, child) => Theme( data: Theme.of(context).copyWith( colorScheme: const ColorScheme.dark( primary: Color(0xFF7B9FFF), onSurface: Colors.white, surface: Color(0xFF1A2035), ), ), child: child!, ), ); if (picked != null) { setState(() => _purchaseDate = picked.toIso8601String().substring(0, 10)); } } void _submit() { if (!_formKey.currentState!.validate()) return; final entry = FilamentModel( documentId: widget.existingEntry?.documentId ?? DateTime.now().millisecondsSinceEpoch.toString(), name: _nameCtrl.text.trim(), type: _selectedType, color: _colorCtrl.text.trim(), weight: double.tryParse(_weightCtrl.text.replaceAll(',', '.')) ?? 0, weightUsed: double.tryParse(_weightUsedCtrl.text.replaceAll(',', '.')) ?? 0, price: double.tryParse(_priceCtrl.text.replaceAll(',', '.')) ?? 0, manufacturer: _manufacturerCtrl.text.trim(), purchaseDate: _purchaseDate, notes: _notesCtrl.text.trim().isEmpty ? null : _notesCtrl.text.trim(), printingTemp: int.tryParse(_printingTempCtrl.text) ?? 0, bedTemp: int.tryParse(_bedTempCtrl.text) ?? 0, ); Navigator.of(context).pop(entry); } @override Widget build(BuildContext context) { final isEdit = widget.existingEntry != null; return ScaleTransition( scale: _fadeScale, child: FadeTransition( opacity: _anim, child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 32), child: ClipRRect( borderRadius: BorderRadius.circular(24), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 24, sigmaY: 24), child: Material( type: MaterialType.transparency, child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(24), border: Border.all( color: Colors.white.withValues(alpha: 0.2), width: 1, ), ), child: Form( key: _formKey, child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ── Titel ── Text( isEdit ? 'Filament bearbeiten' : 'Neues Filament', style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 20), // ── Name ── _GlassField( controller: _nameCtrl, label: 'Name', validator: (v) => v == null || v.trim().isEmpty ? 'Pflichtfeld' : null, ), const SizedBox(height: 12), // ── Typ Dropdown ── _GlassDropdown( value: _selectedType, items: _filamentTypes, label: 'Typ', onChanged: (v) => setState(() => _selectedType = v!), ), const SizedBox(height: 12), // ── Farbe ── _GlassField( controller: _colorCtrl, label: 'Farbe', validator: (v) => v == null || v.trim().isEmpty ? 'Pflichtfeld' : null, ), const SizedBox(height: 12), // ── Hersteller ── _GlassField( controller: _manufacturerCtrl, label: 'Hersteller', validator: (v) => v == null || v.trim().isEmpty ? 'Pflichtfeld' : null, ), const SizedBox(height: 12), // ── Gewicht / Verbraucht ── Row( children: [ Expanded( child: _GlassField( controller: _weightCtrl, label: 'Gewicht (g)', keyboardType: const TextInputType.numberWithOptions( decimal: true, ), validator: (v) => double.tryParse( v?.replaceAll(',', '.') ?? '', ) == null ? 'Ungültig' : null, ), ), const SizedBox(width: 12), Expanded( child: _GlassField( controller: _weightUsedCtrl, label: 'Verbraucht (g)', keyboardType: const TextInputType.numberWithOptions( decimal: true, ), validator: (v) => double.tryParse( v?.replaceAll(',', '.') ?? '', ) == null ? 'Ungültig' : null, ), ), ], ), const SizedBox(height: 12), // ── Preis / Kaufdatum ── Row( children: [ Expanded( child: _GlassField( controller: _priceCtrl, label: 'Preis (€)', keyboardType: const TextInputType.numberWithOptions( decimal: true, ), validator: (v) => double.tryParse( v?.replaceAll(',', '.') ?? '', ) == null ? 'Ungültig' : null, ), ), const SizedBox(width: 12), Expanded( child: GestureDetector( onTap: _pickDate, child: Container( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 16, ), decoration: BoxDecoration( color: Colors.white.withValues( alpha: 0.08, ), borderRadius: BorderRadius.circular( 12, ), border: Border.all( color: Colors.white.withValues( alpha: 0.2, ), ), ), child: Row( children: [ Expanded( child: Text( _purchaseDate.isEmpty ? 'Kaufdatum' : _purchaseDate, style: TextStyle( color: _purchaseDate.isEmpty ? Colors.white38 : Colors.white, fontSize: 14, ), ), ), const Icon( Icons.calendar_today_outlined, color: Colors.white54, size: 16, ), ], ), ), ), ), ], ), const SizedBox(height: 12), // ── Drucktemperatur / Betttemperatur ── Row( children: [ Expanded( child: _GlassField( controller: _printingTempCtrl, label: 'Drucktemp. (°C)', keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, ], validator: (v) => int.tryParse(v ?? '') == null ? 'Ungültig' : null, ), ), const SizedBox(width: 12), Expanded( child: _GlassField( controller: _bedTempCtrl, label: 'Betttemp. (°C)', keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, ], validator: (v) => int.tryParse(v ?? '') == null ? 'Ungültig' : null, ), ), ], ), const SizedBox(height: 12), // ── Notizen (optional) ── _GlassField( controller: _notesCtrl, label: 'Notizen (optional)', maxLines: 3, ), const SizedBox(height: 24), // ── Buttons ── Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text( 'Abbrechen', style: TextStyle(color: Colors.white60), ), ), const SizedBox(width: 12), ElevatedButton( onPressed: _submit, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF7B9FFF), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( isEdit ? 'Speichern' : 'Hinzufügen', ), ), ], ), ], ), ), ), ), ), ), ), ), ), ), ), ); } } // ── Hilfs-Widgets ──────────────────────────────────────────────────────────── class _GlassField extends StatelessWidget { final TextEditingController controller; final String label; final int maxLines; final TextInputType? keyboardType; final List? inputFormatters; final String? Function(String?)? validator; const _GlassField({ required this.controller, required this.label, this.maxLines = 1, this.keyboardType, this.inputFormatters, this.validator, }); @override Widget build(BuildContext context) { return TextFormField( controller: controller, maxLines: maxLines, keyboardType: keyboardType, inputFormatters: inputFormatters, style: const TextStyle(color: Colors.white), validator: validator, decoration: InputDecoration( labelText: label, labelStyle: const TextStyle(color: Colors.white60), filled: true, fillColor: Colors.white.withValues(alpha: 0.08), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.2)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.2)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFF7B9FFF)), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFFFF6B6B)), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFFFF6B6B)), ), ), ); } } class _GlassDropdown extends StatelessWidget { final String value; final List items; final String label; final ValueChanged onChanged; const _GlassDropdown({ required this.value, required this.items, required this.label, required this.onChanged, }); @override Widget build(BuildContext context) { return DropdownButtonFormField( initialValue: value, dropdownColor: const Color(0xFF1A2035), style: const TextStyle(color: Colors.white), decoration: InputDecoration( labelText: label, labelStyle: const TextStyle(color: Colors.white60), filled: true, fillColor: Colors.white.withValues(alpha: 0.08), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.2)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.2)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFF7B9FFF)), ), ), items: items .map((t) => DropdownMenuItem(value: t, child: Text(t))) .toList(), onChanged: onChanged, ); } }