import "package:flutter/material.dart"; import "../main.dart"; import "../models.dart"; import "../format.dart"; import "../theme.dart"; /// Bottom-sheet form to create or edit a car, mirroring the web CarFormModal. /// Pops with the saved [Car] on success, or null if cancelled. class CarFormSheet extends StatefulWidget { final Car? car; // null => create const CarFormSheet({super.key, this.car}); @override State createState() => _CarFormSheetState(); } class _CarFormSheetState extends State { late final Map _c; String _fuelType = ""; DateTime? _buildDate; DateTime? _firstRegistrationDate; bool _saving = false; String? _error; bool get _isEdit => widget.car != null; @override void initState() { super.initState(); final car = widget.car; _fuelType = car?.fuelType ?? ""; _buildDate = (car?.buildDate.isNotEmpty ?? false) ? DateTime.tryParse(car!.buildDate) : null; _firstRegistrationDate = (car?.firstRegistrationDate.isNotEmpty ?? false) ? DateTime.tryParse(car!.firstRegistrationDate) : null; _c = { "name": TextEditingController(text: car?.name ?? ""), "make": TextEditingController(text: car?.make ?? ""), "model": TextEditingController(text: car?.model ?? ""), "year": TextEditingController(text: (car != null && car.year > 0) ? "${car.year}" : ""), "registration": TextEditingController(text: car?.registration ?? ""), "registrationCountry": TextEditingController(text: car?.registrationCountry ?? ""), "vin": TextEditingController(text: car?.vin ?? ""), "oilSpec": TextEditingController(text: car?.oilSpec ?? ""), "transmissionOilSpec": TextEditingController(text: car?.transmissionOilSpec ?? ""), "differentialOilSpec": TextEditingController(text: car?.differentialOilSpec ?? ""), "brakeFluidSpec": TextEditingController(text: car?.brakeFluidSpec ?? ""), "coolantSpec": TextEditingController(text: car?.coolantSpec ?? ""), "currentKm": TextEditingController(text: (car != null && car.currentKm > 0) ? "${car.currentKm}" : ""), "serviceIntervalDays": TextEditingController(text: "${car?.serviceIntervalDays ?? 365}"), "serviceIntervalKm": TextEditingController(text: "${car?.serviceIntervalKm ?? 15000}"), }; } @override void dispose() { for (final c in _c.values) { c.dispose(); } super.dispose(); } int _int(String key, int fallback) => int.tryParse(_c[key]!.text.trim()) ?? fallback; Future _save() async { final name = _c["name"]!.text.trim(); if (name.isEmpty) { setState(() => _error = "Name is required."); return; } setState(() { _saving = true; _error = null; }); final payload = { "name": name, "make": _c["make"]!.text.trim(), "model": _c["model"]!.text.trim(), "year": _int("year", 0), "registration": _c["registration"]!.text.trim(), "registrationCountry": _c["registrationCountry"]!.text.trim(), "vin": _c["vin"]!.text.trim(), "fuelType": _fuelType, "buildDate": _isoOrEmpty(_buildDate), "firstRegistrationDate": _isoOrEmpty(_firstRegistrationDate), "oilSpec": _c["oilSpec"]!.text.trim(), "transmissionOilSpec": _c["transmissionOilSpec"]!.text.trim(), "differentialOilSpec": _c["differentialOilSpec"]!.text.trim(), "brakeFluidSpec": _c["brakeFluidSpec"]!.text.trim(), "coolantSpec": _c["coolantSpec"]!.text.trim(), "currentKm": _int("currentKm", 0), "serviceIntervalDays": _int("serviceIntervalDays", 365), "serviceIntervalKm": _int("serviceIntervalKm", 15000), }; try { final saved = _isEdit ? await apiClient.updateCar(widget.car!.id, payload) : await apiClient.createCar(payload); if (mounted) Navigator.pop(context, saved); } catch (e) { setState(() => _error = e.toString()); } finally { if (mounted) setState(() => _saving = false); } } static String _isoOrEmpty(DateTime? d) => d == null ? "" : "${d.year.toString().padLeft(4, '0')}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}"; Widget _field(String key, String label, {TextInputType? keyboard, TextCapitalization caps = TextCapitalization.none}) { return Padding( padding: const EdgeInsets.only(bottom: 10), child: TextField( controller: _c[key], keyboardType: keyboard, textCapitalization: caps, decoration: InputDecoration(labelText: label, border: const OutlineInputBorder(), isDense: true), ), ); } /// A tappable read-only field that opens a date picker. Shows a clear button /// when a date is set, otherwise a calendar icon. Widget _dateField(String label, DateTime? value, ValueChanged onChanged) { return Padding( padding: const EdgeInsets.only(bottom: 10), child: InkWell( onTap: () async { final picked = await showDatePicker( context: context, initialDate: value ?? DateTime.now(), firstDate: DateTime(1950), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (picked != null) setState(() => onChanged(picked)); }, child: InputDecorator( decoration: InputDecoration( labelText: label, border: const OutlineInputBorder(), isDense: true, suffixIcon: value == null ? const Icon(Icons.calendar_today, size: 18) : IconButton( icon: const Icon(Icons.clear, size: 18), onPressed: () => setState(() => onChanged(null)), ), ), child: Text(value == null ? "—" : formatDate(value)), ), ), ); } @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only( left: 16, right: 16, top: 16, bottom: MediaQuery.of(context).viewInsets.bottom + 16, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(_isEdit ? "Edit car" : "Add a car", style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(height: 12), if (_error != null) Padding( padding: const EdgeInsets.only(bottom: 8), child: Text(_error!, style: const TextStyle(color: DriverVault.danger)), ), _field("name", "Name *", caps: TextCapitalization.words), Row(children: [ Expanded(child: _field("make", "Make")), const SizedBox(width: 8), Expanded(child: _field("model", "Model")), ]), Row(children: [ Expanded(child: _field("year", "Year", keyboard: TextInputType.number)), const SizedBox(width: 8), Expanded(child: _field("registration", "Registration")), ]), _field("registrationCountry", "Registration country", caps: TextCapitalization.words), _field("vin", "VIN"), Padding( padding: const EdgeInsets.only(bottom: 10), child: DropdownButtonFormField( initialValue: _fuelType.isEmpty ? null : _fuelType, decoration: const InputDecoration( labelText: "Fuel type", border: OutlineInputBorder(), isDense: true), items: const [ DropdownMenuItem(value: "petrol", child: Text("Petrol (gasoline)")), DropdownMenuItem(value: "diesel", child: Text("Diesel")), DropdownMenuItem(value: "hybrid", child: Text("Hybrid")), DropdownMenuItem(value: "electric", child: Text("Electric")), ], onChanged: (v) => setState(() => _fuelType = v ?? ""), ), ), Row(children: [ Expanded(child: _dateField("Build date", _buildDate, (v) => _buildDate = v)), const SizedBox(width: 8), Expanded( child: _dateField("First registration", _firstRegistrationDate, (v) => _firstRegistrationDate = v)), ]), _field("oilSpec", "Engine oil spec"), Row(children: [ Expanded(child: _field("transmissionOilSpec", "Transmission oil spec")), const SizedBox(width: 8), Expanded(child: _field("differentialOilSpec", "Differential oil spec")), ]), Row(children: [ Expanded(child: _field("brakeFluidSpec", "Brake fluid spec")), const SizedBox(width: 8), Expanded(child: _field("coolantSpec", "Coolant spec")), ]), _field("currentKm", "Current odometer (km)", keyboard: TextInputType.number), Row(children: [ Expanded( child: _field("serviceIntervalDays", "Service interval (days)", keyboard: TextInputType.number)), const SizedBox(width: 8), Expanded( child: _field("serviceIntervalKm", "Service interval (km)", keyboard: TextInputType.number)), ]), const SizedBox(height: 8), SizedBox( width: double.infinity, child: FilledButton( onPressed: _saving ? null : _save, child: Text(_saving ? "Saving…" : (_isEdit ? "Save changes" : "Add car")), ), ), ], ), ), ); } }