payfrit-app/lib/screens/address_edit_screen.dart
John Mizerek 2491c961e0 Add address management and user account features
- Add delivery address list, add, edit, delete, set default functionality
- Add order history screen
- Add profile settings screen
- Add account screen with avatar upload
- Update restaurant select gradient direction
- Add states API endpoint for address forms
- Fix table names (tt_States)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 20:22:37 -08:00

269 lines
9.6 KiB
Dart

import 'package:flutter/material.dart';
import '../services/api.dart';
class AddressEditScreen extends StatefulWidget {
const AddressEditScreen({super.key});
@override
State<AddressEditScreen> createState() => _AddressEditScreenState();
}
class _AddressEditScreenState extends State<AddressEditScreen> {
final _formKey = GlobalKey<FormState>();
final _labelController = TextEditingController();
final _line1Controller = TextEditingController();
final _line2Controller = TextEditingController();
final _cityController = TextEditingController();
final _zipCodeController = TextEditingController();
bool _isLoading = true;
bool _isSaving = false;
bool _setAsDefault = false;
int? _selectedStateId;
List<StateInfo> _states = [];
DeliveryAddress? _existingAddress;
bool get _isEditing => _existingAddress != null;
@override
void initState() {
super.initState();
_loadStates();
}
Future<void> _loadStates() async {
try {
final states = await Api.getStates();
if (mounted) {
setState(() {
_states = states;
// Default to Texas (44) if no address is being edited
if (_selectedStateId == null && _existingAddress == null) {
_selectedStateId = 44;
}
_isLoading = false;
});
}
} catch (e) {
debugPrint('Error loading states: $e');
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final args = ModalRoute.of(context)?.settings.arguments;
if (args is DeliveryAddress && _existingAddress == null) {
_existingAddress = args;
_labelController.text = args.label;
_line1Controller.text = args.line1;
_line2Controller.text = args.line2;
_cityController.text = args.city;
_zipCodeController.text = args.zipCode;
_selectedStateId = args.stateId > 0 ? args.stateId : 44;
_setAsDefault = args.isDefault;
}
}
@override
void dispose() {
_labelController.dispose();
_line1Controller.dispose();
_line2Controller.dispose();
_cityController.dispose();
_zipCodeController.dispose();
super.dispose();
}
Future<void> _saveAddress() async {
if (!_formKey.currentState!.validate()) return;
if (_selectedStateId == null) return;
setState(() => _isSaving = true);
try {
await Api.addDeliveryAddress(
line1: _line1Controller.text.trim(),
line2: _line2Controller.text.trim().isEmpty ? null : _line2Controller.text.trim(),
city: _cityController.text.trim(),
stateId: _selectedStateId!,
zipCode: _zipCodeController.text.trim(),
label: _labelController.text.trim().isEmpty ? null : _labelController.text.trim(),
setAsDefault: _setAsDefault,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_isEditing ? 'Address updated' : 'Address added'),
backgroundColor: Colors.green,
),
);
Navigator.pop(context, true);
}
} catch (e) {
debugPrint('Error saving address: $e');
if (mounted) {
setState(() => _isSaving = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to save: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isEditing ? 'Edit Address' : 'Add Address'),
actions: [
TextButton(
onPressed: _isSaving || _isLoading ? null : _saveAddress,
child: _isSaving
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Save'),
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _labelController,
decoration: const InputDecoration(
labelText: 'Label (optional)',
hintText: 'e.g., Home, Work, Office',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.label_outline),
),
textCapitalization: TextCapitalization.words,
),
const SizedBox(height: 16),
TextFormField(
controller: _line1Controller,
decoration: const InputDecoration(
labelText: 'Street Address',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.location_on_outlined),
),
textCapitalization: TextCapitalization.words,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter a street address';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _line2Controller,
decoration: const InputDecoration(
labelText: 'Apt, Suite, Unit (optional)',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.apartment),
),
textCapitalization: TextCapitalization.words,
),
const SizedBox(height: 16),
TextFormField(
controller: _cityController,
decoration: const InputDecoration(
labelText: 'City',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.location_city),
),
textCapitalization: TextCapitalization.words,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter a city';
}
return null;
},
),
const SizedBox(height: 16),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 2,
child: DropdownButtonFormField<int>(
value: _selectedStateId,
decoration: const InputDecoration(
labelText: 'State',
border: OutlineInputBorder(),
),
items: _states.map((state) {
return DropdownMenuItem<int>(
value: state.stateId,
child: Text(state.abbr),
);
}).toList(),
onChanged: (value) {
if (value != null) {
setState(() => _selectedStateId = value);
}
},
validator: (value) {
if (value == null || value <= 0) {
return 'Select a state';
}
return null;
},
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: TextFormField(
controller: _zipCodeController,
decoration: const InputDecoration(
labelText: 'ZIP Code',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Enter ZIP';
}
if (value.trim().length < 5) {
return 'Invalid ZIP';
}
return null;
},
),
),
],
),
const SizedBox(height: 24),
SwitchListTile(
title: const Text('Set as default address'),
subtitle: const Text('Use this address for deliveries by default'),
value: _setAsDefault,
onChanged: (value) => setState(() => _setAsDefault = value),
contentPadding: EdgeInsets.zero,
),
],
),
),
),
);
}
}