Patient registration complete with authentication flow fixed Co-authored-by: Benoy Bose <benoybose@gmail.com> Co-authored-by: Jipson George <152465898+Jipson-cosq@users.noreply.github.com> Reviewed-on: cosqnet/telemednet#3 Reviewed-by: Benoy Bose <benoybose@cosq.net> Co-authored-by: DhanshCOSQ <dhanshas@cosq.net> Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
382 lines
12 KiB
Dart
382 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:country_state_city_picker/country_state_city_picker.dart';
|
|
import 'package:telemednet/controller/patient_controller.dart';
|
|
|
|
class PatientAddressScreen extends StatefulWidget {
|
|
final PatientController? controller;
|
|
|
|
const PatientAddressScreen({super.key, required this.controller});
|
|
|
|
@override
|
|
State<PatientAddressScreen> createState() => _PatientAddressScreenState();
|
|
}
|
|
|
|
class _PatientAddressScreenState extends State<PatientAddressScreen> {
|
|
late PatientController _controller;
|
|
late TextEditingController _houseNoController;
|
|
late TextEditingController _lineController;
|
|
late TextEditingController _townController;
|
|
late TextEditingController _pincodeController;
|
|
late TextEditingController _otherLabelController;
|
|
final String country = 'India';
|
|
String? state;
|
|
String? city;
|
|
String? addressType;
|
|
Map<String, String> _errors = {};
|
|
bool _hasErrors = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = widget.controller ?? PatientController();
|
|
_loadSavedData();
|
|
}
|
|
|
|
void _loadSavedData() {
|
|
final address = _controller.model.address;
|
|
_houseNoController = TextEditingController(text: address.houseNo ?? '');
|
|
_lineController = TextEditingController(text: address.line ?? '');
|
|
_townController = TextEditingController(text: address.town ?? '');
|
|
_pincodeController = TextEditingController(text: address.pincode ?? '');
|
|
_otherLabelController =
|
|
TextEditingController(text: address.otherLabel ?? '');
|
|
state = address.state;
|
|
city = address.city;
|
|
addressType = address.addressType;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Address'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: _saveAndExit,
|
|
child: const Text('Done', style: TextStyle(color: Colors.blue)),
|
|
),
|
|
],
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionContainer(
|
|
'Address Information',
|
|
Column(
|
|
children: [
|
|
_buildTextField(
|
|
'House No.',
|
|
_houseNoController,
|
|
(value) => widget.controller!.updateHouseNo(value),
|
|
icon: Icons.home_outlined,
|
|
errorKey: 'houseNo',
|
|
),
|
|
_buildTextField(
|
|
'Address Line',
|
|
_lineController,
|
|
(value) => widget.controller!.updateLine(value),
|
|
icon: Icons.location_on_outlined,
|
|
errorKey: 'line',
|
|
),
|
|
_buildTextField(
|
|
'Town (Optional)',
|
|
_townController,
|
|
(value) => widget.controller!.updateTown(value),
|
|
icon: Icons.location_city_outlined,
|
|
),
|
|
_buildTextField(
|
|
'Pincode',
|
|
_pincodeController,
|
|
(value) => widget.controller!.updatePincode(value),
|
|
icon: Icons.pin_drop_outlined,
|
|
errorKey: 'pincode',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
_buildSectionContainer(
|
|
'Location',
|
|
Column(
|
|
children: [
|
|
_buildCountrySelection(),
|
|
const SizedBox(height: 10),
|
|
SelectState(
|
|
onCountryChanged: (value) {
|
|
setState(() {});
|
|
widget.controller!.updateCountry('India');
|
|
},
|
|
onStateChanged: (value) {
|
|
setState(() {
|
|
state = value;
|
|
});
|
|
widget.controller!.updateState(value);
|
|
},
|
|
onCityChanged: (value) {
|
|
setState(() {
|
|
city = value;
|
|
});
|
|
widget.controller!.updateCity(value);
|
|
},
|
|
),
|
|
const SizedBox(height: 20),
|
|
if (state != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 10),
|
|
child: Text('State: $state',
|
|
style: const TextStyle(
|
|
fontSize: 14, color: Colors.black87)),
|
|
),
|
|
if (city != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 10),
|
|
child: Text('City: $city',
|
|
style: const TextStyle(
|
|
fontSize: 14, color: Colors.black87)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
_buildSectionContainer(
|
|
'Address Type',
|
|
Column(
|
|
children: [
|
|
_buildAddressTypeChips(),
|
|
if (addressType == 'Other')
|
|
_buildTextField(
|
|
'Other Label',
|
|
_otherLabelController,
|
|
(value) => widget.controller!.updateOtherLabel(value),
|
|
icon: Icons.label_outline,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
bool _validateFields() {
|
|
setState(() {
|
|
_errors.clear();
|
|
_hasErrors = false;
|
|
|
|
if (_houseNoController.text.trim().isEmpty) {
|
|
_errors['houseNo'] = 'House No. is required';
|
|
_hasErrors = true;
|
|
}
|
|
|
|
if (_lineController.text.trim().isEmpty) {
|
|
_errors['line'] = 'Address Line is required';
|
|
_hasErrors = true;
|
|
}
|
|
|
|
final pincode = _pincodeController.text.trim();
|
|
if (pincode.isEmpty) {
|
|
_errors['pincode'] = 'Pincode is required';
|
|
_hasErrors = true;
|
|
} else if (!RegExp(r'^\d{6}$').hasMatch(pincode)) {
|
|
_errors['pincode'] = 'Enter a valid 6-digit pincode';
|
|
_hasErrors = true;
|
|
}
|
|
|
|
if (state == null || state!.isEmpty) {
|
|
_errors['state'] = 'State is required';
|
|
_hasErrors = true;
|
|
}
|
|
|
|
if (city == null || city!.isEmpty) {
|
|
_errors['city'] = 'City is required';
|
|
_hasErrors = true;
|
|
}
|
|
|
|
if (addressType == null || addressType!.isEmpty) {
|
|
_errors['addressType'] = 'Please select an address type';
|
|
_hasErrors = true;
|
|
}
|
|
|
|
if (addressType == 'Other' && _otherLabelController.text.trim().isEmpty) {
|
|
_errors['otherLabel'] = 'Please specify other label';
|
|
_hasErrors = true;
|
|
}
|
|
});
|
|
|
|
return !_hasErrors;
|
|
}
|
|
|
|
Widget _buildSectionContainer(String title, Widget content) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.blueGrey.withOpacity(0.5),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 10),
|
|
content,
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTextField(
|
|
String label,
|
|
TextEditingController controller,
|
|
Function(String) onChanged, {
|
|
required IconData icon,
|
|
String? errorKey,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TextField(
|
|
controller: controller,
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
prefixIcon: Icon(icon,
|
|
color: _errors.containsKey(errorKey)
|
|
? Colors.red
|
|
: Colors.blueAccent),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color: _errors.containsKey(errorKey) ? Colors.red : Colors.grey,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color: _errors.containsKey(errorKey) ? Colors.red : Colors.grey,
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color: _errors.containsKey(errorKey)
|
|
? Colors.red
|
|
: Colors.blueAccent,
|
|
),
|
|
),
|
|
errorText: _errors[errorKey],
|
|
),
|
|
onChanged: onChanged,
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildCountrySelection() {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
child: const Row(
|
|
children: [
|
|
Text(
|
|
'Country:',
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
|
),
|
|
SizedBox(width: 8),
|
|
Text('India', style: TextStyle(fontSize: 16)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildAddressTypeChips() {
|
|
return Wrap(
|
|
spacing: 8.0,
|
|
children: ['Home', 'Office', 'Other'].map((String type) {
|
|
return ChoiceChip(
|
|
label: Text(type),
|
|
selected: addressType == type,
|
|
onSelected: (bool selected) {
|
|
setState(() {
|
|
addressType = selected ? type : addressType;
|
|
});
|
|
widget.controller!.updateAddressType(addressType!);
|
|
},
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
void _saveAndExit() {
|
|
if (_validateFields()) {
|
|
widget.controller!.updateHouseNo(_houseNoController.text);
|
|
widget.controller!.updateLine(_lineController.text);
|
|
widget.controller!.updateTown(_townController.text);
|
|
widget.controller!.updatePincode(_pincodeController.text);
|
|
widget.controller!.updateCountry(country);
|
|
widget.controller!.updateState(state ?? '');
|
|
widget.controller!.updateCity(city ?? '');
|
|
widget.controller!.updateAddressType(addressType ?? '');
|
|
widget.controller!.updateOtherLabel(_otherLabelController.text);
|
|
widget.controller!.updatePatientData();
|
|
Navigator.pop(context, true);
|
|
} else {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Row(
|
|
children: [
|
|
Icon(Icons.error_outline, color: Colors.red),
|
|
SizedBox(width: 8),
|
|
Text('Validation Errors'),
|
|
],
|
|
),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: _errors.entries
|
|
.map((error) => Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Text(
|
|
'• ${error.value}',
|
|
style: const TextStyle(color: Colors.red),
|
|
),
|
|
))
|
|
.toList(),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('OK'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_houseNoController.dispose();
|
|
_lineController.dispose();
|
|
_townController.dispose();
|
|
_pincodeController.dispose();
|
|
_otherLabelController.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|