medora-provider/lib/screens/patientScreens/appoinmentBooking/consultation_booking_screen.dart
DhanshCOSQ b57523599c feature/medora-55 (#6)
Booking physical consultation ,  bugs fixed and Profile picture adding using firebase storage is complete.

Co-authored-by: Jipson George <152465898+Jipson-cosq@users.noreply.github.com>
Reviewed-on: cosqnet/telemednet#6
Co-authored-by: DhanshCOSQ <dhanshas@cosq.net>
Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
2024-11-05 08:22:13 +00:00

841 lines
26 KiB
Dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:medora/data/models/patient.dart';
import 'package:medora/data/services/consultation_booking_service.dart';
import 'package:medora/data/services/patient_registration_service.dart';
import 'package:medora/route/route_names.dart';
import 'package:medora/widgets/alert_screen.dart';
class ConsultationBookingScreen extends StatefulWidget {
final Doctor doctor;
final ConsultationCenter selectedConsultation;
final DateTime selectedDate;
final String selectedTime;
const ConsultationBookingScreen({
super.key,
required this.doctor,
required this.selectedConsultation,
required this.selectedDate,
required this.selectedTime,
});
@override
State<ConsultationBookingScreen> createState() =>
_ConsultationBookingScreenState();
}
class _ConsultationBookingScreenState extends State<ConsultationBookingScreen> {
PatientModel? selectedPatient;
List<PatientModel> familyMembers = [];
FamilyMember? selectedFamilyMember;
bool isLoading = true;
final TextEditingController _nameController = TextEditingController();
final TextEditingController _relationController = TextEditingController();
DateTime? _selectedDateOfBirth;
String _selectedGender = 'Male';
@override
void dispose() {
_nameController.dispose();
_relationController.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_loadPatientProfile();
}
Future<void> _loadPatientProfile() async {
setState(() => isLoading = true);
try {
final currentPatient = await PatientProfileService.getPatientProfile();
if (currentPatient != null) {
setState(() {
selectedPatient = currentPatient;
});
}
} catch (e) {
print('Error loading patient data: $e');
} finally {
setState(() => isLoading = false);
}
}
String get formattedAddress {
final parts = [
widget.selectedConsultation.floorBuilding,
widget.selectedConsultation.street,
widget.selectedConsultation.city,
widget.selectedConsultation.state,
widget.selectedConsultation.postalCode
].where((part) => part != null && part.isNotEmpty).toList();
return parts.join(', ');
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F7FF),
appBar: _buildAppBar(context),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildAppointmentCard(),
const SizedBox(height: 24),
_buildDoctorDetails(),
const SizedBox(height: 24),
_buildLocationDetails(),
const SizedBox(height: 24),
_buildPaymentDetails(),
const SizedBox(height: 24),
_buildInClinicAppointmentText(),
const SizedBox(height: 24),
_buildConfirmButton(context),
],
),
),
),
);
}
Widget _buildInClinicAppointmentText() {
String patientName =
selectedFamilyMember?.name ?? selectedPatient?.name ?? 'Select Patient';
String relation = selectedFamilyMember?.relation != null
? ' (${selectedFamilyMember!.relation})'
: '';
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: RichText(
text: TextSpan(
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.black87,
),
children: [
const TextSpan(text: 'In-clinic appointment for '),
TextSpan(
text: '$patientName$relation',
style: const TextStyle(
fontWeight: FontWeight.w600,
),
),
],
),
),
),
TextButton(
onPressed: _showPatientSelectionDialog,
child: Text(
'Change',
style: GoogleFonts.poppins(
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
}
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black87),
onPressed: () => Navigator.pop(context),
),
title: Text(
'Booking Overview',
style: GoogleFonts.poppins(
color: Colors.black87,
fontWeight: FontWeight.w600,
fontSize: 20,
),
),
centerTitle: true,
);
}
Widget _buildAppointmentCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Row(
children: [
const Icon(Icons.calendar_today, color: Colors.white),
const SizedBox(width: 12),
Text(
DateFormat('EEEE, MMMM d').format(widget.selectedDate),
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
const Icon(Icons.access_time, color: Colors.white),
const SizedBox(width: 12),
Text(
widget.selectedTime,
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
);
}
Widget _buildDoctorDetails() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
widget.doctor.profileImage!,
width: 80,
height: 80,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 80,
height: 80,
color: Colors.grey[300],
child: Icon(Icons.person, size: 40, color: Colors.grey[600]),
);
},
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.doctor.firstName ?? '',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
Text(
widget.doctor.speciality!,
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
Text(
'${widget.doctor.yearsOfExperience} years experience',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
],
),
);
}
Widget _buildLocationDetails() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Location',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
formattedAddress,
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
'Average consultation time: ${widget.selectedConsultation.averageDurationMinutes} minutes',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
);
}
Widget _buildPaymentDetails() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Payment Details',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Consultation Fee',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
Text(
'${widget.selectedConsultation.consultationFee ?? "500"}',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
);
}
Widget _buildConfirmButton(BuildContext context) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
// Handle payment and booking confirmation
_showConfirmationDialog(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Confirm & Pay',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
);
}
void _showConfirmationDialog(BuildContext context) async {
if (selectedPatient == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please select a patient for the appointment'),
backgroundColor: Colors.red,
),
);
return;
}
final bookingService = BookingService();
final currentUser = FirebaseAuth.instance.currentUser;
// Get the correct patient name based on selection
final patientName = selectedFamilyMember != null
? selectedFamilyMember!.name
: selectedPatient!.name;
try {
if (context.mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(
child: CircularProgressIndicator(),
),
);
}
final bookingId = await bookingService.createBooking(
doctorName: widget.doctor.firstName ?? 'Doctor',
patientId: currentUser!.uid,
patientName: patientName ?? 'Patient',
location: formattedAddress,
appointmentDate: widget.selectedDate,
appointmentTime: widget.selectedTime,
consultationFee:
int.parse(widget.selectedConsultation.consultationFee ?? "500"),
specialization: widget.doctor.speciality!,
);
if (context.mounted) {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AlertScreen(
arguments: AlertArguments(
title: 'Booking Confirmed',
message:
'Your in-clinic appointment has been successfully booked for $patientName. Booking ID: ${bookingId.substring(0, 8)}\n\nPlease complete the payment to confirm your appointment.',
actionTitle: 'View Appointments',
type: AlertType.success,
onActionPressed: () {
Navigator.pushReplacementNamed(
context, RouteNames.patientDashboardScreen);
},
),
),
),
);
}
} catch (e) {
if (context.mounted) {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AlertScreen(
arguments: AlertArguments(
title: 'Booking Failed',
message: 'Unable to create booking. ${e.toString()}',
actionTitle: 'Try Again',
type: AlertType.error,
onActionPressed: () {
Navigator.of(context).pop();
},
),
),
),
);
}
}
}
Future<void> _showAddFamilyMemberDialog() async {
_nameController.clear();
_relationController.clear();
setState(() {
_selectedDateOfBirth = null;
_selectedGender = 'Male';
});
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) => StatefulBuilder(
builder: (BuildContext context, StateSetter setDialogState) {
return AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Text(
'Add Family Member',
style: GoogleFonts.poppins(fontWeight: FontWeight.w600),
),
content: AnimatedContainer(
duration: const Duration(milliseconds: 300),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Full Name',
labelStyle: GoogleFonts.poppins(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(
Icons.person_outline,
color: Colors.blue,
),
),
),
const SizedBox(height: 16),
TextFormField(
controller: _relationController,
decoration: InputDecoration(
labelText: 'Relation',
labelStyle: GoogleFonts.poppins(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(
Icons.family_restroom,
color: Colors.blue,
),
),
),
const SizedBox(height: 16),
InkWell(
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: ColorScheme.light(
primary: Colors.blue,
onPrimary: Colors.white,
surface: Colors.grey[100]!,
),
),
child: child!,
);
},
);
if (picked != null) {
setDialogState(() {
_selectedDateOfBirth = picked;
});
}
},
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(Icons.calendar_today,
color: Colors.blue),
const SizedBox(width: 12),
Text(
_selectedDateOfBirth != null
? DateFormat('dd/MM/yyyy')
.format(_selectedDateOfBirth!)
: 'Select Date of Birth',
style: GoogleFonts.poppins(),
),
],
),
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12),
),
child: DropdownButtonHideUnderline(
child: DropdownButtonFormField<String>(
value: _selectedGender,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.person_outline,
color: Colors.blue),
border: InputBorder.none,
labelStyle: GoogleFonts.poppins(),
),
items: ['Male', 'Female', 'Other']
.map((gender) => DropdownMenuItem(
value: gender,
child: Text(gender,
style: GoogleFonts.poppins()),
))
.toList(),
onChanged: (value) {
if (value != null) {
setDialogState(() => _selectedGender = value);
}
},
),
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'Cancel',
style: GoogleFonts.poppins(color: Colors.grey),
),
),
ElevatedButton(
onPressed: () => _addFamilyMember(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Add Member',
style: GoogleFonts.poppins(color: Colors.white),
),
),
],
);
},
),
);
}
Future<void> _addFamilyMember(BuildContext context) async {
if (_nameController.text.isEmpty ||
_relationController.text.isEmpty ||
_selectedDateOfBirth == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Please fill in all fields',
style: GoogleFonts.poppins(),
),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
return;
}
try {
final newFamilyMember = FamilyMember(
name: _nameController.text,
relation: _relationController.text,
gender: _selectedGender,
dateOfBirth: _selectedDateOfBirth,
);
if (selectedPatient != null) {
selectedPatient!.familyMembers.add(newFamilyMember);
await PatientProfileService.updatePatientProfile(selectedPatient!);
setState(() {
selectedFamilyMember = newFamilyMember;
});
}
if (context.mounted) {
Navigator.pop(context);
_showPatientSelectionDialog();
}
} catch (e) {
if (context.mounted) {
Navigator.pop(context); // Pop add family member dialog
}
}
}
void _showPatientSelectionDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Text(
'Select Patient',
style: GoogleFonts.poppins(fontWeight: FontWeight.w600),
),
content: SizedBox(
width: double.maxFinite,
child: ListView(
shrinkWrap: true,
children: [
// Main patient
_buildPatientTile(
name: selectedPatient?.name ?? '',
subtitle: 'Primary Patient',
isSelected: selectedFamilyMember == null,
onTap: () {
setState(() {
selectedFamilyMember = null;
});
Navigator.pop(context);
},
),
const Divider(),
// Family members
...selectedPatient?.familyMembers.map(
(member) => _buildPatientTile(
name: member.name ?? '',
subtitle: member.relation ?? '',
isSelected: selectedFamilyMember == member,
onTap: () {
setState(() {
selectedFamilyMember = member;
});
Navigator.pop(context);
},
),
) ??
[],
const Divider(),
ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.person_add, color: Colors.blue),
),
title: Text(
'Add Family Member',
style: GoogleFonts.poppins(
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
onTap: () {
Navigator.pop(context);
_showAddFamilyMemberDialog();
},
),
],
),
),
),
);
}
Widget _buildPatientTile({
required String name,
required String subtitle,
required bool isSelected,
required VoidCallback onTap,
}) {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelected
? Colors.blue.withOpacity(0.1)
: Colors.grey.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.person,
color: isSelected ? Colors.blue : Colors.grey,
),
),
title: Text(
name,
style: GoogleFonts.poppins(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected ? Colors.blue : Colors.black87,
),
),
subtitle: Text(
subtitle,
style: GoogleFonts.poppins(
color: Colors.grey[600],
),
),
trailing: isSelected
? const Icon(Icons.check_circle, color: Colors.blue)
: null,
onTap: onTap,
);
}
}