first commit

This commit is contained in:
Courtney Arnold 2026-08-08 08:22:14 -05:00
commit e630f2a3ec
80 changed files with 3889 additions and 0 deletions

View file

@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/vehicle.dart';
import '../services/app_state.dart';
/// Add/edit form for a vehicle. Pass an existing [vehicle] to edit it, or
/// omit it to create a new one.
class AddEditVehicleScreen extends StatefulWidget {
final Vehicle? vehicle;
const AddEditVehicleScreen({super.key, this.vehicle});
@override
State<AddEditVehicleScreen> createState() => _AddEditVehicleScreenState();
}
class _AddEditVehicleScreenState extends State<AddEditVehicleScreen> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController _makeController;
late final TextEditingController _modelController;
late final TextEditingController _colorController;
late final TextEditingController _plateController;
bool get _isEditing => widget.vehicle != null;
@override
void initState() {
super.initState();
_makeController = TextEditingController(text: widget.vehicle?.make ?? '');
_modelController = TextEditingController(text: widget.vehicle?.model ?? '');
_colorController = TextEditingController(text: widget.vehicle?.color ?? '');
_plateController = TextEditingController(text: widget.vehicle?.licensePlate ?? '');
}
@override
void dispose() {
_makeController.dispose();
_modelController.dispose();
_colorController.dispose();
_plateController.dispose();
super.dispose();
}
Future<void> _save() async {
if (!_formKey.currentState!.validate()) return;
final appState = context.read<AppState>();
if (_isEditing) {
await appState.updateVehicle(widget.vehicle!.copyWith(
make: _makeController.text.trim(),
model: _modelController.text.trim(),
color: _colorController.text.trim(),
licensePlate: _plateController.text.trim(),
));
} else {
await appState.addVehicle(
make: _makeController.text.trim(),
model: _modelController.text.trim(),
color: _colorController.text.trim(),
licensePlate: _plateController.text.trim(),
);
}
if (mounted) Navigator.of(context).pop();
}
Future<void> _confirmDelete() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete vehicle?'),
content: Text(
'This will also delete all fuel entries and receipt photos logged for '
'${widget.vehicle!.displayName}. This cannot be undone.',
),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel')),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Delete', style: TextStyle(color: Colors.red)),
),
],
),
);
if (confirmed == true && mounted) {
await context.read<AppState>().deleteVehicle(widget.vehicle!.id);
if (mounted) {
Navigator.of(context)
..pop()
..pop();
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isEditing ? 'Edit Vehicle' : 'Add Vehicle'),
actions: [
if (_isEditing)
IconButton(
icon: const Icon(Icons.delete_outline),
tooltip: 'Delete vehicle',
onPressed: _confirmDelete,
),
],
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
controller: _makeController,
decoration: const InputDecoration(labelText: 'Make', hintText: 'e.g. Ford'),
textCapitalization: TextCapitalization.words,
validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null,
),
const SizedBox(height: 12),
TextFormField(
controller: _modelController,
decoration: const InputDecoration(labelText: 'Model', hintText: 'e.g. F-150'),
textCapitalization: TextCapitalization.words,
validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null,
),
const SizedBox(height: 12),
TextFormField(
controller: _colorController,
decoration: const InputDecoration(labelText: 'Color', hintText: 'e.g. Red'),
textCapitalization: TextCapitalization.words,
validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null,
),
const SizedBox(height: 12),
TextFormField(
controller: _plateController,
decoration: const InputDecoration(labelText: 'License Plate'),
textCapitalization: TextCapitalization.characters,
validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null,
),
const SizedBox(height: 24),
FilledButton(
onPressed: _save,
child: Text(_isEditing ? 'Save Changes' : 'Add Vehicle'),
),
],
),
),
);
}
}