onedirve and google drive

This commit is contained in:
Courtney Arnold 2026-08-14 17:30:31 -05:00
parent a9bcec09b1
commit 901ec678a3
12 changed files with 373 additions and 29 deletions

View file

@ -9,6 +9,7 @@ import '../services/app_state.dart';
import '../services/ocr_service.dart';
import '../services/vin_parser.dart';
import '../widgets/image_source_sheet.dart';
import 'vehicle_detail_screen.dart';
/// Add/edit form for a vehicle. Pass an existing [vehicle] to edit it, or
/// omit it to create a new one.
@ -28,6 +29,7 @@ class _AddEditVehicleScreenState extends State<AddEditVehicleScreen> {
bool _saving = false;
bool _scanningVin = false;
bool _checkingDuplicateVin = false;
String? _error;
bool get _isEditing => widget.vehicle != null;
@ -84,6 +86,7 @@ class _AddEditVehicleScreenState extends State<AddEditVehicleScreen> {
if (vin != null) {
_vinController.text = vin;
await _checkForExistingVin();
} else if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
@ -93,6 +96,62 @@ class _AddEditVehicleScreenState extends State<AddEditVehicleScreen> {
}
}
/// Field-level counterpart to [_checkForExistingVin]'s dialog: catches
/// "Required" as before, plus flags the field itself as invalid when the
/// VIN belongs to another active vehicle — excluding this vehicle's own
/// id while editing, same as [AppState.updateVehicle]'s check, so editing
/// a vehicle without touching its VIN doesn't flag itself.
String? _validateVin(String? value) {
final vin = value?.trim() ?? '';
if (vin.isEmpty) return 'Required';
final existing = context.read<AppState>().vehicleByVin(vin);
if (existing != null && existing.id != widget.vehicle?.id) {
return 'A vehicle with VIN "$vin" already exists.';
}
return null;
}
/// Only relevant when adding a new vehicle — editing one already starts
/// with its own VIN pre-filled, which would trivially "match itself".
/// Guarded against overlapping calls so rapid edits (or the scan-fill
/// path firing right after `onChanged`) can't stack multiple dialogs.
Future<void> _checkForExistingVin() async {
if (_isEditing || _checkingDuplicateVin) return;
final vin = _vinController.text.trim();
if (vin.isEmpty) return;
final existing = context.read<AppState>().vehicleByVin(vin);
if (existing == null) return;
_checkingDuplicateVin = true;
final openExisting = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Vehicle already exists'),
content: Text('Vehicle with the VIN $vin already exists, would you like to open that vehicle?'),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(false), child: const Text('No')),
TextButton(onPressed: () => Navigator.of(context).pop(true), child: const Text('Yes')),
],
),
);
_checkingDuplicateVin = false;
if (!mounted) return;
if (openExisting != true) {
// Declining to open the existing vehicle leaves them here with a
// still-duplicate VIN — surface that on the field itself rather than
// only catching it later at Save.
_formKey.currentState?.validate();
return;
}
Navigator.of(context)
..pop()
..push(MaterialPageRoute(builder: (_) => VehicleDetailScreen(vehicleId: existing.id)));
}
Future<void> _save() async {
if (!_formKey.currentState!.validate()) return;
@ -205,7 +264,8 @@ class _AddEditVehicleScreenState extends State<AddEditVehicleScreen> {
),
),
textCapitalization: TextCapitalization.characters,
validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null,
validator: _validateVin,
onChanged: _isEditing ? null : (_) => _checkForExistingVin(),
),
if (_error != null) ...[
const SizedBox(height: 12),