From 370107db7289f53be6ac06b8f097df4ca2acc0dc Mon Sep 17 00:00:00 2001 From: aswincosq Date: Wed, 26 Feb 2025 15:31:44 +0530 Subject: [PATCH] Split the buisiness logic, to keep only doctor related buisiness --- lib/controllers/patient_controller.dart | 104 --- lib/data/models/patient.dart | 135 --- lib/data/services/navigation_service.dart | 65 +- .../patient_registration_service.dart | 198 ---- lib/data_service.dart | 2 +- lib/route/routes.dart | 110 +-- lib/screens/authentication/launch_screen.dart | 4 +- .../authentication/sign_up_screen.dart | 14 +- .../doctor_personal_profile_screen.dart | 2 +- .../consultation_booking_screen.dart | 842 ------------------ .../consultation_time_screen.dart | 553 ------------ .../consultations_center_screen.dart | 312 ------- .../doctor_details_screen.dart | 346 ------- .../doctors_list_screen.dart | 387 -------- .../speciality_screen.dart | 426 --------- .../patient_dashboard_screen.dart | 61 -- .../patient_home_screen.dart | 695 --------------- .../patient_profile_screen.dart | 197 ---- .../patient_landing_screen.dart | 94 -- .../family_members_edit_screen.dart | 285 ------ .../patient_adress_screen.dart | 381 -------- .../patient_family_members_screen.dart | 308 ------- .../patient_registration_screen.dart | 680 -------------- lib/telemednet_app.dart | 3 +- pubspec.lock | 38 +- pubspec.yaml | 2 +- 26 files changed, 85 insertions(+), 6159 deletions(-) delete mode 100644 lib/controllers/patient_controller.dart delete mode 100644 lib/data/models/patient.dart delete mode 100644 lib/data/services/patient_registration_service.dart delete mode 100644 lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart delete mode 100644 lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart delete mode 100644 lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart delete mode 100644 lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart delete mode 100644 lib/screens/patient_screens/appoinment_bookings/doctors_list_screen.dart delete mode 100644 lib/screens/patient_screens/appoinment_bookings/speciality_screen.dart delete mode 100644 lib/screens/patient_screens/patient_dashboard/patient_dashboard_screen.dart delete mode 100644 lib/screens/patient_screens/patient_dashboard/patient_home_screen.dart delete mode 100644 lib/screens/patient_screens/patient_dashboard/patient_profile_screen.dart delete mode 100644 lib/screens/patient_screens/patient_landing_screen.dart delete mode 100644 lib/screens/patient_screens/registration_screens/family_members_edit_screen.dart delete mode 100644 lib/screens/patient_screens/registration_screens/patient_adress_screen.dart delete mode 100644 lib/screens/patient_screens/registration_screens/patient_family_members_screen.dart delete mode 100644 lib/screens/patient_screens/registration_screens/patient_registration_screen.dart diff --git a/lib/controllers/patient_controller.dart b/lib/controllers/patient_controller.dart deleted file mode 100644 index e408271..0000000 --- a/lib/controllers/patient_controller.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:medora/data/models/patient.dart'; -import '../data/services/patient_registration_service.dart'; - -class PatientController { - final PatientModel model = PatientModel(); - Map validationErrors = {}; - - void clearValidationErrors() { - validationErrors.clear(); - } - - void updateName(String name) { - model.name = name; - } - - void updatePhoneNumber(String phoneNumber) { - model.phoneNumber = phoneNumber; - } - - void updateGender(String gender) { - model.gender = gender; - } - - void updateDateOfBirth(DateTime dateOfBirth) { - model.dateOfBirth = dateOfBirth; - } - - void updateProfileImage(String imagePath) { - model.profileImagePath = imagePath; - } - - void updateHouseNo(String houseNo) { - model.address.houseNo = houseNo; - } - - void updateLine(String line) { - model.address.line = line; - } - - void updateTown(String town) { - model.address.town = town; - } - - void updatePincode(String pincode) { - model.address.pincode = pincode; - } - - void updateCountry(String country) { - model.address.country = country; - } - - void updateState(String state) { - model.address.state = state; - } - - void updateCity(String city) { - model.address.city = city; - } - - void updateAddressType(String addressType) { - model.address.addressType = addressType; - } - - void updateOtherLabel(String otherLabel) { - model.address.otherLabel = otherLabel; - } - - void addFamilyMember(FamilyMember member) { - model.familyMembers.add(member); - } - - void updateFamilyMember(int index, FamilyMember member) { - if (index >= 0 && index < model.familyMembers.length) { - model.familyMembers[index] = member; - } - } - - void deleteFamilyMember(int index) { - if (index >= 0 && index < model.familyMembers.length) { - model.familyMembers.removeAt(index); - } - } - - Future savePatientData() async { - return await PatientProfileService.savePatientProfile(this); - } - - Future loadPatientData() async { - PatientModel? loadedModel = await PatientProfileService.getPatientProfile(); - if (loadedModel != null) { - model.updateFrom(loadedModel); - return true; - } - return false; - } - - Future updatePatientData() async { - return await PatientProfileService.updatePatientProfile(model); - } - - Future deletePatientData() async { - return await PatientProfileService.deletePatientProfile(); - } -} diff --git a/lib/data/models/patient.dart b/lib/data/models/patient.dart deleted file mode 100644 index 5dcd4e0..0000000 --- a/lib/data/models/patient.dart +++ /dev/null @@ -1,135 +0,0 @@ -class PatientModel { - String? name; - String? phoneNumber; - String? gender; - DateTime? dateOfBirth; - String? profileImagePath; - String? profileImageUrl; - PatientAddress address; - - List familyMembers = []; - - PatientModel() : address = PatientAddress(); - - Map toJson() { - return { - 'name': name, - 'phoneNumber': phoneNumber, - 'gender': gender, - 'dateOfBirth': dateOfBirth?.toIso8601String(), - 'profileImagePath': profileImagePath, - 'profileImageUrl': profileImageUrl, - 'address': address.toJson(), - 'familyMembers': familyMembers.map((member) => member.toJson()).toList(), - }; - } - - PatientModel.fromJson(Map json) - : address = PatientAddress() { - name = json['name']; - phoneNumber = json['phoneNumber']; - gender = json['gender']; - dateOfBirth = json['dateOfBirth'] != null - ? DateTime.parse(json['dateOfBirth']) - : null; - profileImagePath = json['profileImagePath']; - profileImageUrl = json['profileImageUrl']; - if (json['address'] != null) { - address = - PatientAddress.fromJson(json['address'] as Map); - } - if (json['familyMembers'] != null) { - familyMembers = (json['familyMembers'] as List) - .map((memberJson) => FamilyMember.fromJson(memberJson)) - .toList(); - } - } - - void updateFrom(PatientModel other) { - name = other.name; - phoneNumber = other.phoneNumber; - gender = other.gender; - dateOfBirth = other.dateOfBirth; - profileImagePath = other.profileImagePath; - profileImageUrl = other.profileImageUrl; - address = other.address; - familyMembers = other.familyMembers; - } -} - -class FamilyMember { - String? name; - String? relation; - String? gender; - DateTime? dateOfBirth; - - FamilyMember({this.name, this.relation, this.gender, this.dateOfBirth}); - - Map toJson() { - return { - 'name': name, - 'relation': relation, - 'gender': gender, - 'dateOfBirth': dateOfBirth?.toIso8601String(), - }; - } - - FamilyMember.fromJson(Map json) { - name = json['name']; - relation = json['relation']; - gender = json['gender']; - dateOfBirth = json['dateOfBirth'] != null - ? DateTime.parse(json['dateOfBirth']) - : null; - } -} - -class PatientAddress { - String? houseNo; - String? line; - String? town; - String? pincode; - String? country; - String? state; - String? city; - String? addressType; - String? otherLabel; - - PatientAddress({ - this.houseNo, - this.line, - this.town, - this.pincode, - this.country, - this.state, - this.city, - this.addressType, - this.otherLabel, - }); - - Map toJson() { - return { - 'houseNo': houseNo, - 'line': line, - 'town': town, - 'pincode': pincode, - 'country': country, - 'state': state, - 'city': city, - 'addressType': addressType, - 'otherLabel': otherLabel, - }; - } - - PatientAddress.fromJson(Map json) { - houseNo = json['houseNo']; - line = json['line']; - town = json['town']; - pincode = json['pincode']; - country = json['country']; - state = json['state']; - city = json['city']; - addressType = json['addressType']; - otherLabel = json['otherLabel']; - } -} diff --git a/lib/data/services/navigation_service.dart b/lib/data/services/navigation_service.dart index 7ca3be5..ae91a39 100644 --- a/lib/data/services/navigation_service.dart +++ b/lib/data/services/navigation_service.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:medora/data/models/telemed_user.dart'; import 'package:medora/data/services/data_service.dart'; import 'package:medora/data/services/doctor_profile_service.dart'; -import 'package:medora/data/services/patient_registration_service.dart'; import 'package:medora/route/route_names.dart'; class NavigationService { @@ -17,23 +16,15 @@ class NavigationService { return; } - switch (userProfile.role.toLowerCase()) { - case 'doctor': - if (context.mounted) { - handleDoctorNavigation(context); - } + if (userProfile.role.toLowerCase() != 'doctor') { + if (context.mounted) { + Navigator.pushReplacementNamed(context, RouteNames.launch); + return; + } + } - break; - case 'patient': - if (context.mounted) { - handlePatientNavigation(context); - } - - break; - default: - if (context.mounted) { - Navigator.pushReplacementNamed(context, RouteNames.launch); - } + if (context.mounted) { + await handleDoctorNavigation(context); } } catch (e) { print('Error in handleUserNavigation: $e'); @@ -57,25 +48,25 @@ class NavigationService { } } - static Future handlePatientNavigation(BuildContext context) async { - try { - final patientProfile = await PatientProfileService.getPatientProfile(); + // static Future handlePatientNavigation(BuildContext context) async { + // try { + // final patientProfile = await PatientProfileService.getPatientProfile(); - if (context.mounted) { - if (patientProfile != null) { - Navigator.pushReplacementNamed( - context, RouteNames.patientDashboardScreen); - } else { - Navigator.pushReplacementNamed( - context, RouteNames.patientLandingScreen); - } - } - } catch (e) { - print('Error in handlePatientNavigation: $e'); - if (context.mounted) { - Navigator.pushReplacementNamed( - context, RouteNames.patientLandingScreen); - } - } - } + // if (context.mounted) { + // if (patientProfile != null) { + // Navigator.pushReplacementNamed( + // context, RouteNames.patientDashboardScreen); + // } else { + // Navigator.pushReplacementNamed( + // context, RouteNames.patientLandingScreen); + // } + // } + // } catch (e) { + // print('Error in handlePatientNavigation: $e'); + // if (context.mounted) { + // Navigator.pushReplacementNamed( + // context, RouteNames.patientLandingScreen); + // } + // } + // } } diff --git a/lib/data/services/patient_registration_service.dart b/lib/data/services/patient_registration_service.dart deleted file mode 100644 index fd91ded..0000000 --- a/lib/data/services/patient_registration_service.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'dart:io'; -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:firebase_storage/firebase_storage.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:medora/controllers/patient_controller.dart'; -import 'package:medora/data/models/patient.dart'; -import 'package:path/path.dart' as path; - -class PatientProfileService { - static final String patientProfileCollectionName = - dotenv.env['PATIENT_PROFILE_COLLECTION_NAME']!; - static final FirebaseFirestore db = FirebaseFirestore.instance; - static final FirebaseStorage storage = FirebaseStorage.instanceFor( - bucket: dotenv.env['FIREBASE_STORAGE_BUCKET']!); - - static Future uploadProfileImage(File imageFile) async { - try { - final User? user = FirebaseAuth.instance.currentUser; - if (user == null) { - print('No user logged in'); - return null; - } - final String uid = user.uid; - final String fileName = - 'profile_${uid}_${DateTime.now().millisecondsSinceEpoch}${path.extension(imageFile.path)}'; - final Reference storageRef = - storage.ref().child('profile_images/$fileName'); - final UploadTask uploadTask = storageRef.putFile( - imageFile, - SettableMetadata( - contentType: 'image/${path.extension(imageFile.path).substring(1)}', - customMetadata: { - 'userId': uid, - 'uploadedAt': DateTime.now().toIso8601String(), - }, - ), - ); - final TaskSnapshot snapshot = await uploadTask; - final String downloadUrl = await snapshot.ref.getDownloadURL(); - - print('Profile image uploaded successfully'); - return downloadUrl; - } catch (e) { - print('Error uploading profile image: $e'); - return null; - } - } - - static Future deleteProfileImage(String imageUrl) async { - try { - final Reference storageRef = storage.refFromURL(imageUrl); - await storageRef.delete(); - print('Profile image deleted successfully'); - return true; - } catch (e) { - print('Error deleting profile image: $e'); - return false; - } - } - - static Future savePatientProfile(PatientController controller) async { - try { - final User? user = FirebaseAuth.instance.currentUser; - if (user == null) { - print('No user logged in'); - return false; - } - - final String uid = user.uid; - final PatientModel patientData = controller.model; - String? imageUrl; - if (patientData.profileImagePath != null) { - final File imageFile = File(patientData.profileImagePath!); - imageUrl = await uploadProfileImage(imageFile); - if (imageUrl == null) { - print('Failed to upload profile image'); - return false; - } - } - - final Map patientJson = patientData.toJson(); - patientJson['createdAt'] = FieldValue.serverTimestamp(); - patientJson['updatedAt'] = FieldValue.serverTimestamp(); - patientJson['uid'] = uid; - patientJson['profileImageUrl'] = imageUrl; - - await db - .collection(patientProfileCollectionName) - .doc(uid) - .set(patientJson); - - print('Patient profile saved successfully'); - return true; - } catch (e) { - print('Error saving patient profile: $e'); - return false; - } - } - - static Future updatePatientProfile(PatientModel patient) async { - try { - final User? user = FirebaseAuth.instance.currentUser; - if (user == null) { - print('No user logged in'); - return false; - } - - final String uid = user.uid; - String? imageUrl; - if (patient.profileImagePath != null) { - final DocumentSnapshot oldDoc = - await db.collection(patientProfileCollectionName).doc(uid).get(); - if (oldDoc.exists) { - final oldData = oldDoc.data() as Map; - final String? oldImageUrl = oldData['profileImageUrl']; - if (oldImageUrl != null) { - await deleteProfileImage(oldImageUrl); - } - } - final File imageFile = File(patient.profileImagePath!); - imageUrl = await uploadProfileImage(imageFile); - if (imageUrl == null) { - print('Failed to upload new profile image'); - return false; - } - } - - final Map patientJson = patient.toJson(); - patientJson['updatedAt'] = FieldValue.serverTimestamp(); - if (imageUrl != null) { - patientJson['profileImageUrl'] = imageUrl; - } - - await db - .collection(patientProfileCollectionName) - .doc(uid) - .update(patientJson); - - print('Patient profile updated successfully'); - return true; - } catch (e) { - print('Error updating patient profile: $e'); - return false; - } - } - - static Future deletePatientProfile() async { - try { - final User? user = FirebaseAuth.instance.currentUser; - if (user == null) { - print('No user logged in'); - return false; - } - - final String uid = user.uid; - final DocumentSnapshot doc = - await db.collection(patientProfileCollectionName).doc(uid).get(); - if (doc.exists) { - final data = doc.data() as Map; - final String? imageUrl = data['profileImageUrl']; - if (imageUrl != null) { - await deleteProfileImage(imageUrl); - } - } - - await db.collection(patientProfileCollectionName).doc(uid).delete(); - - print('Patient profile deleted successfully'); - return true; - } catch (e) { - print('Error deleting patient profile: $e'); - return false; - } - } - - static Future getPatientProfile() async { - try { - final User? user = FirebaseAuth.instance.currentUser; - if (user == null) { - print('No user logged in'); - return null; - } - final String uid = user.uid; - final DocumentSnapshot doc = - await db.collection(patientProfileCollectionName).doc(uid).get(); - if (!doc.exists) { - print('No patient profile found for this user'); - return null; - } - final data = doc.data() as Map; - return PatientModel.fromJson(data); - } catch (e) { - print('Error fetching patient profile: $e'); - return null; - } - } -} diff --git a/lib/data_service.dart b/lib/data_service.dart index c2bb877..7c2a96c 100644 --- a/lib/data_service.dart +++ b/lib/data_service.dart @@ -1,7 +1,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:telemednet/telemed_user.dart'; +import 'package:medora/data/models/telemed_user.dart'; class DataService { static final String profileCollectionName = diff --git a/lib/route/routes.dart b/lib/route/routes.dart index 5f73931..06df502 100644 --- a/lib/route/routes.dart +++ b/lib/route/routes.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:medora/controllers/consultation_center_controller.dart'; import 'package:medora/data/models/consultation_center.dart'; import 'package:medora/data/models/doctor.dart'; +import 'package:medora/data/services/navigation_service.dart'; import 'package:medora/screens/authentication/launch_screen.dart'; -// import 'package:medora/data/telemed_user.dart'; -import 'package:medora/controllers/patient_controller.dart'; import 'package:medora/route/route_names.dart'; +import 'package:medora/screens/authentication/sign_up_screen.dart'; import 'package:medora/screens/common/loading_screen.dart'; import 'package:medora/screens/doctor_screen/doctor_consultation_schedule/business_center_screen.dart'; import 'package:medora/screens/doctor_screen/doctor_consultation_schedule/center_fee_and_duration_screen.dart'; @@ -26,41 +26,45 @@ import 'package:medora/screens/doctor_screen/doctor_profile_screens/experience_s import 'package:medora/screens/doctor_screen/doctor_profile_screens/profile_description_screen.dart'; import 'package:medora/screens/doctor_screen/doctor_profile_screens/qualifications_screen.dart'; import 'package:medora/screens/doctor_screen/doctor_profile_screens/specialities_selection_screen.dart'; -import 'package:medora/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart'; -import 'package:medora/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart'; -import 'package:medora/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart'; -import 'package:medora/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart'; -import 'package:medora/screens/patient_screens/appoinment_bookings/doctors_list_screen.dart'; -import 'package:medora/screens/patient_screens/appoinment_bookings/speciality_screen.dart'; -import 'package:medora/screens/patient_screens/patient_dashboard/patient_dashboard_screen.dart'; -import 'package:medora/screens/patient_screens/patient_dashboard/patient_home_screen.dart'; -import 'package:medora/screens/patient_screens/patient_dashboard/patient_profile_screen.dart'; -import 'package:medora/screens/patient_screens/registration_screens/patient_adress_screen.dart'; -import 'package:medora/screens/patient_screens/registration_screens/patient_family_members_screen.dart'; -import 'package:medora/screens/patient_screens/registration_screens/patient_registration_screen.dart'; import 'package:medora/screens/splash_screen.dart'; import '../controllers/doctor_controller.dart'; -import '../screens/patient_screens/patient_landing_screen.dart'; -import '../screens/patient_screens/registration_screens/family_members_edit_screen.dart'; final Map routes = { - RouteNames.launch: (context) => const LaunchScreen(), + // RouteNames.launch: (context) => const LaunchScreen(), RouteNames.signIn: (context) => SignInScreen( providers: [EmailAuthProvider(), PhoneAuthProvider()], + showAuthActionSwitch: false, + footerBuilder: (context, action) { + return Padding( + padding: const EdgeInsets.only(top: 16), + child: TextButton( + onPressed: () { + Navigator.pushNamed(context, RouteNames.signUp); + }, + child: const Text( + "Don't have an account? Sign up", + ), + ), + ); + }, + actions: [ + AuthStateChangeAction((context, state) { + print("Sign in successful"); + NavigationService.handleUserNavigation(context); + }), + AuthStateChangeAction((context, state) { + print("Sign in failed: ${state.exception}"); + }), + ], ), - RouteNames.signUp: (context) => const RegisterScreen(), + RouteNames.signUp: (context) => const SignUpScreen(), // RouteNames.userProfile: (context) { // var user = ModalRoute.of(context)!.settings.arguments as TelemedUser?; // return UserProfileScreen(user: user); // }, // RouteNames.userHome: (context) => const UserScreen(), - RouteNames.profileUpload: (context) => const ProfileUploadPage(), - RouteNames.patientLandingScreen: (context) => const PatientLandingScreen(), - RouteNames.patientHomeScreen: (context) => const PatientHomeScreen(), RouteNames.doctorLandingScreen: (context) => const DoctorLandingScreen(), - RouteNames.patientRegistrationScreen: (context) => - const PatientRegistrationScreen(), RouteNames.qualificationsScreen: (context) { final controller = ModalRoute.of(context)!.settings.arguments as DoctorController?; @@ -68,6 +72,7 @@ final Map routes = { controller: controller ?? DoctorController(), // Provide fallback ); }, + RouteNames.profileUpload: (context) => const ProfileUploadPage(), RouteNames.doctorAddressScreen: (context) { final controller = ModalRoute.of(context)!.settings.arguments as DoctorController?; @@ -110,65 +115,6 @@ final Map routes = { controller: controller ?? DoctorController(), ); }, - RouteNames.patientAdressScreen: (context) { - final controller = - ModalRoute.of(context)!.settings.arguments as PatientController; - return PatientAddressScreen(controller: controller); - }, - RouteNames.patientFamilyMembersScreen: (context) { - final controller = - ModalRoute.of(context)!.settings.arguments as PatientController; - return PatientFamilyMembersScreen(controller: controller); - }, - RouteNames.familyMembersEditScreen: (context) { - final controller = - ModalRoute.of(context)!.settings.arguments as PatientController; - return FamilyMembersEditScreen(controller: controller); - }, - RouteNames.patientprofileScreen: (context) => const PatientProfileScreen(), - RouteNames.patientDashboardScreen: (context) => - const PatientDashboardScreen(), - RouteNames.specialityScreen: (context) => const SpecialtyScreen(), - RouteNames.doctorListScreen: (context) { - final args = - ModalRoute.of(context)!.settings.arguments as Map; - return DoctorsListScreen( - specialty: args['specialty'] as String, - ); - }, - RouteNames.doctorDetailsScreen: (context) { - final args = - ModalRoute.of(context)!.settings.arguments as Map; - return DoctorDetailsScreen( - doctor: args['doctor'] as Doctor, - preloadedImage: args['imageProvider'] as ImageProvider?, - ); - }, - RouteNames.consultationCenterScreen: (context) { - final args = - ModalRoute.of(context)!.settings.arguments as Map; - return ConsultationsCenterScreen( - doctor: args['doctor'] as Doctor, - ); - }, - RouteNames.consultationTimeScreen: (context) { - final args = - ModalRoute.of(context)!.settings.arguments as Map; - return ConsultationTimeScreen( - doctor: args['doctor'] as Doctor, - selectedConsultation: args['selectedConsultation'] as ConsultationCenter, - ); - }, - RouteNames.consultationBookingScreen: (context) { - final args = - ModalRoute.of(context)!.settings.arguments as Map; - return ConsultationBookingScreen( - doctor: args['doctor'] as Doctor, - selectedConsultation: args['selectedConsultation'] as ConsultationCenter, - selectedDate: args['selectedDate'] as DateTime, - selectedTime: args['selectedTime'] as String, - ); - }, RouteNames.doctorDashbordScreen: (context) => const DoctorDashboardScreen(), RouteNames.doctorHomeScreen: (context) => const DoctorDashboardHomeScreen(), RouteNames.doctorPersonalProfileScreen: (context) => diff --git a/lib/screens/authentication/launch_screen.dart b/lib/screens/authentication/launch_screen.dart index 58675ec..f4737ac 100644 --- a/lib/screens/authentication/launch_screen.dart +++ b/lib/screens/authentication/launch_screen.dart @@ -185,9 +185,7 @@ class _LaunchScreenState extends State { void _navigateToSignUp() { Navigator.of(context).push( MaterialPageRoute( - builder: (context) => SignUpScreen( - selectedUserType: selectedUserType!, - ), + builder: (context) => const SignUpScreen(), ), ); } diff --git a/lib/screens/authentication/sign_up_screen.dart b/lib/screens/authentication/sign_up_screen.dart index a464803..042b1e3 100644 --- a/lib/screens/authentication/sign_up_screen.dart +++ b/lib/screens/authentication/sign_up_screen.dart @@ -7,11 +7,11 @@ import 'package:medora/data/services/navigation_service.dart'; import 'package:medora/widgets/primary_button.dart'; class SignUpScreen extends StatefulWidget { - final String selectedUserType; + // final String selectedUserType; const SignUpScreen({ super.key, - required this.selectedUserType, + // required this.selectedUserType, }); @override @@ -160,7 +160,7 @@ class _SignUpScreenState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - 'Register as ${widget.selectedUserType}', + 'Register as doctor', style: Theme.of(context).textTheme.headlineLarge?.copyWith( fontWeight: FontWeight.bold, ), @@ -299,17 +299,13 @@ class _SignUpScreenState extends State { final result = await DataService.createUserProfile( email: _emailController.text.trim(), password: _passwordController.text, - userType: widget.selectedUserType, + userType: 'doctor', phoneNumber: _completePhoneNumber, ); if (mounted) { if (result['success']) { - if (widget.selectedUserType.toLowerCase() == 'doctor') { - await NavigationService.handleDoctorNavigation(context); - } else { - await NavigationService.handlePatientNavigation(context); - } + await NavigationService.handleDoctorNavigation(context); } else { _showErrorSnackBar(result['message']); } diff --git a/lib/screens/doctor_screen/doctor_dashboard/doctor_personal_profile_screen.dart b/lib/screens/doctor_screen/doctor_dashboard/doctor_personal_profile_screen.dart index 81f7f38..f8a9589 100644 --- a/lib/screens/doctor_screen/doctor_dashboard/doctor_personal_profile_screen.dart +++ b/lib/screens/doctor_screen/doctor_dashboard/doctor_personal_profile_screen.dart @@ -187,7 +187,7 @@ class _DoctorPersonalProfileScreen extends State { try { await _auth.signOut(); if (mounted) { - Navigator.of(context).pushReplacementNamed(RouteNames.launch); + Navigator.of(context).pushReplacementNamed(RouteNames.signIn); } } catch (e) { print("Error signing out: $e"); diff --git a/lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart b/lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart deleted file mode 100644 index f1e9102..0000000 --- a/lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart +++ /dev/null @@ -1,842 +0,0 @@ -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 createState() => - _ConsultationBookingScreenState(); -} - -class _ConsultationBookingScreenState extends State { - PatientModel? selectedPatient; - List 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 _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.profileImageUrl!, - 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( - doctorId: widget.doctor.uid!, - profileImageUrl: widget.doctor.profileImageUrl!, - 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 _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( - 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 _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, - ); - } -} diff --git a/lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart b/lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart deleted file mode 100644 index 7ee76c7..0000000 --- a/lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart +++ /dev/null @@ -1,553 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:medora/data/models/consultation_center.dart'; -import 'package:medora/data/models/doctor.dart'; -import 'package:intl/intl.dart'; -import 'package:medora/route/route_names.dart'; - -class ConsultationTimeScreen extends StatefulWidget { - final Doctor doctor; - final ConsultationCenter selectedConsultation; - - const ConsultationTimeScreen({ - super.key, - required this.doctor, - required this.selectedConsultation, - }); - - @override - State createState() => _ConsultationTimeScreenState(); -} - -class _ConsultationTimeScreenState extends State { - DateTime? selectedDate; - String? selectedTime; - - List getTimeSlotsForDay(String dayName) { - try { - final schedule = widget.selectedConsultation.weeklySchedule?.firstWhere( - (schedule) => schedule.day == dayName, - orElse: () => AvailabilitySchedule( - day: dayName, - timeSlots: [], - ), - ); - return schedule?.timeSlots ?? []; - } catch (e) { - debugPrint('Error getting time slots: $e'); - return []; - } - } - - DateTime? parseTimeString(String? timeStr) { - if (timeStr == null) return null; - - try { - // Try parsing 12-hour format first - return DateFormat('h:mm a').parse(timeStr); - } catch (e) { - try { - // Try parsing 24-hour format - return DateFormat('HH:mm').parse(timeStr); - } catch (e) { - debugPrint('Error parsing time: $timeStr'); - return null; - } - } - } - - 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(), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildLocationInfo(), - const SizedBox(height: 24), - _buildDateSelection(), - const SizedBox(height: 24), - _buildTimeSlots(), - ], - ), - ), - ), - ); - } - - PreferredSizeWidget _buildAppBar() { - return AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black87), - onPressed: () => Navigator.pop(context), - ), - title: Text( - 'Select Date & Time', - style: GoogleFonts.poppins( - color: Colors.black87, - fontWeight: FontWeight.w600, - fontSize: 20, - ), - ), - centerTitle: true, - ); - } - - Widget _buildLocationInfo() { - 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: [ - Row( - children: [ - 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], - ), - ), - ], - ), - ), - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Image.network( - widget.doctor.profileImageUrl!, - width: 60, - height: 60, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Container( - width: 60, - height: 60, - color: Colors.grey[300], - child: - Icon(Icons.person, size: 30, color: Colors.grey[600]), - ); - }, - ), - ), - ], - ), - const Divider(height: 24), - Text( - 'Selected Location', - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Text( - widget.selectedConsultation.city ?? '', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 4), - Text( - 'Average consultation time: ${widget.selectedConsultation.averageDurationMinutes}', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ], - ), - ); - } - - Widget _buildDateSelection() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Select Date', - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 12), - SizedBox( - height: 100, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: 20, // Show next 20 days - itemBuilder: (context, index) { - final date = DateTime.now().add(Duration(days: index)); - final isSelected = selectedDate?.day == date.day && - selectedDate?.month == date.month && - selectedDate?.year == date.year; - final isAvailable = _isDateAvailable(date); - - return GestureDetector( - onTap: isAvailable - ? () { - setState(() { - selectedDate = date; - selectedTime = null; // Reset time when date changes - }); - } - : null, - child: Container( - width: 70, - margin: const EdgeInsets.only(right: 12), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: isSelected - ? Colors.blue - : isAvailable - ? Colors.white - : Colors.grey[200], - borderRadius: BorderRadius.circular(16), - boxShadow: isAvailable - ? [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ] - : null, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - DateFormat('EEE').format(date).toUpperCase(), - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w500, - color: isSelected - ? Colors.white - : isAvailable - ? Colors.grey[600] - : Colors.grey[400], - ), - ), - const SizedBox(height: 8), - Text( - date.day.toString(), - style: GoogleFonts.poppins( - fontSize: 18, - fontWeight: FontWeight.w600, - color: isSelected - ? Colors.white - : isAvailable - ? Colors.black87 - : Colors.grey[400], - ), - ), - ], - ), - ), - ); - }, - ), - ), - ], - ); - } - - Widget _buildTimeSlots() { - if (selectedDate == null) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.5), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey.withOpacity(0.2)), - ), - child: Center( - child: Text( - 'Please select a date to view available time slots', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ), - ); - } - - final dayName = DateFormat('EEEE').format(selectedDate!); - final timeSlots = getTimeSlotsForDay(dayName); - final allTimeSlots = _generateTimeSlots(timeSlots); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Select Time', - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 12), - 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: Wrap( - spacing: 12, - runSpacing: 12, - children: allTimeSlots.map((time) { - final isSelected = selectedTime == time; - final isAvailable = _isTimeSlotAvailable(time); - - return GestureDetector( - onTap: isAvailable - ? () { - Navigator.pushNamed( - context, - RouteNames.consultationBookingScreen, - arguments: { - 'doctor': widget.doctor, - 'selectedConsultation': widget.selectedConsultation, - 'selectedDate': selectedDate, - 'selectedTime': time - }, - ); - } - : null, - child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - decoration: BoxDecoration( - color: isSelected - ? Colors.blue - : isAvailable - ? Colors.white - : Colors.grey[200], - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isSelected - ? Colors.blue - : isAvailable - ? Colors.grey.withOpacity(0.2) - : Colors.grey.withOpacity(0.1), - ), - ), - child: Text( - time, - style: GoogleFonts.poppins( - fontSize: 14, - fontWeight: FontWeight.w500, - color: isSelected - ? Colors.white - : isAvailable - ? Colors.black87 - : Colors.grey[400], - ), - ), - ), - ); - }).toList(), - ), - ), - ], - ); - } - - bool _isDateAvailable(DateTime date) { - final dayName = DateFormat('EEEE').format(date); - return widget.selectedConsultation.weeklySchedule - ?.any((schedule) => schedule.day == dayName) ?? - false; - } - - List _generateTimeSlots(List timeSlots) { - final slots = []; - final timeFormat = DateFormat('h:mm a'); - - for (var slot in timeSlots) { - final startTime = parseTimeString(slot.startTime); - final endTime = parseTimeString(slot.endTime); - - if (startTime == null || endTime == null) continue; - - var currentTime = startTime; - while (currentTime.isBefore(endTime)) { - slots.add(timeFormat.format(currentTime)); - currentTime = currentTime.add(const Duration(minutes: 30)); - } - } - - return slots; - } - - bool _isTimeSlotAvailable(String time) { - final now = DateTime.now(); - - if (selectedDate == null) return false; - - // Parse the time slot - final timeSlot = parseTimeString(time); - if (timeSlot == null) return false; - - // Create a DateTime combining selected date and time - final slotDateTime = DateTime( - selectedDate!.year, - selectedDate!.month, - selectedDate!.day, - timeSlot.hour, - timeSlot.minute, - ); - - // Check if the slot is in the past - if (slotDateTime.isBefore(now)) return false; - - // Here you would typically check against your booking database - // For now, returning true for future slots - return true; - } - - void _handleBooking() { - showDialog( - context: context, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - title: Text( - 'Confirm Booking', - style: GoogleFonts.poppins( - fontWeight: FontWeight.w600, - ), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildConfirmationDetail( - 'Doctor', - widget.doctor.firstName!, - ), - const SizedBox(height: 8), - _buildConfirmationDetail( - 'Location', - widget.selectedConsultation.city!, - ), - const SizedBox(height: 8), - _buildConfirmationDetail( - 'Date', - DateFormat('EEEE, MMMM d').format(selectedDate!), - ), - const SizedBox(height: 8), - _buildConfirmationDetail( - 'Time', - selectedTime!, - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Cancel', - style: GoogleFonts.poppins( - color: Colors.grey[600], - ), - ), - ), - ElevatedButton( - onPressed: () { - Navigator.pop(context); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: Text( - 'Confirm', - style: GoogleFonts.poppins( - color: Colors.white, - ), - ), - ), - ], - ), - ); - } - - Widget _buildConfirmationDetail(String label, String value) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 80, - child: Text( - '$label:', - style: GoogleFonts.poppins( - fontWeight: FontWeight.w500, - color: Colors.grey[600], - ), - ), - ), - Expanded( - child: Text( - value, - style: GoogleFonts.poppins( - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ); - } -} diff --git a/lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart b/lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart deleted file mode 100644 index d03f98b..0000000 --- a/lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:medora/data/models/doctor.dart'; -import 'package:medora/data/models/consultation_center.dart'; -import 'package:medora/data/services/consultation_center_service.dart'; -import 'package:medora/route/route_names.dart'; - -class ConsultationsCenterScreen extends StatefulWidget { - final Doctor doctor; - - const ConsultationsCenterScreen({ - super.key, - required this.doctor, - }); - - @override - State createState() => - _ConsultationsCenterScreenState(); -} - -class _ConsultationsCenterScreenState extends State { - List _consultationCenters = []; - bool _isLoading = true; - String? _error; - - @override - void initState() { - super.initState(); - _fetchDoctorConsultationCenters(); - } - - Future _fetchDoctorConsultationCenters() async { - try { - setState(() { - _isLoading = true; - _error = null; - }); - - if (widget.doctor.uid == null) { - throw Exception('Doctor UID is missing'); - } - - final centers = - await ConsultationCenterService.getDoctorConsultationCenters( - widget.doctor.uid!, - ); - - if (mounted) { - setState(() { - _consultationCenters = centers; - _isLoading = false; - }); - } - } catch (e) { - if (mounted) { - setState(() { - _error = e.toString(); - _isLoading = false; - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to load consultation centers: $e')), - ); - } - } - } - - String _formatAddress(ConsultationCenter center) { - List addressParts = []; - - if (center.floorBuilding != null && center.floorBuilding!.isNotEmpty) { - addressParts.add(center.floorBuilding!); - } - if (center.street != null && center.street!.isNotEmpty) { - addressParts.add(center.street!); - } - if (center.city != null && center.city!.isNotEmpty) { - addressParts.add(center.city!); - } - - return addressParts.join(', '); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xFFF5F7FF), - appBar: _buildAppBar(), - body: RefreshIndicator( - onRefresh: _fetchDoctorConsultationCenters, - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildDoctorInfo(), - const SizedBox(height: 24), - _buildConsultationLocations(), - ], - ), - ), - ), - ), - ); - } - - PreferredSizeWidget _buildAppBar() { - return AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black87), - onPressed: () => Navigator.pop(context), - ), - title: Text( - 'Select Location', - style: GoogleFonts.poppins( - color: Colors.black87, - fontWeight: FontWeight.w600, - fontSize: 20, - ), - ), - centerTitle: true, - ); - } - - Widget _buildDoctorInfo() { - 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.profileImageUrl!, - 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 _buildConsultationLocations() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Select Location', - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 12), - if (_isLoading) - const Center(child: CircularProgressIndicator()) - else if (_error != null) - Center( - child: Column( - children: [ - Text( - 'Error loading centers', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.red, - ), - ), - TextButton( - onPressed: _fetchDoctorConsultationCenters, - child: const Text('Retry'), - ), - ], - ), - ) - else if (_consultationCenters.isEmpty) - Center( - child: Text( - 'No consultation centers available', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ) - else - SizedBox( - height: 120, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: _consultationCenters.length, - itemBuilder: (context, index) { - final center = _consultationCenters[index]; - - return GestureDetector( - onTap: () { - Navigator.pushNamed( - context, - RouteNames.consultationTimeScreen, - arguments: { - 'doctor': widget.doctor, - 'selectedConsultation': center, - }, - ); - }, - child: Container( - width: 200, - margin: const EdgeInsets.only(right: 16), - 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( - _formatAddress(center), - style: GoogleFonts.poppins( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.black87, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const Spacer(), - if (center.averageDurationMinutes != null) - Text( - 'Average time: ${center.averageDurationMinutes} mins', - style: GoogleFonts.poppins( - fontSize: 12, - color: Colors.grey[600], - ), - ), - if (center.consultationFee != null) - Text( - 'Fee: ${center.consultationFee}', - style: GoogleFonts.poppins( - fontSize: 12, - color: Colors.grey[600], - ), - ), - ], - ), - ), - ); - }, - ), - ), - ], - ); - } -} diff --git a/lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart b/lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart deleted file mode 100644 index 02e1b87..0000000 --- a/lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart +++ /dev/null @@ -1,346 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:medora/data/models/doctor.dart'; -import 'package:medora/route/route_names.dart'; -import 'package:shimmer/shimmer.dart'; - -class DoctorDetailsScreen extends StatefulWidget { - final Doctor doctor; - final ImageProvider? preloadedImage; - - const DoctorDetailsScreen({ - super.key, - required this.doctor, - this.preloadedImage, - }); - - @override - State createState() => _DoctorDetailsScreenState(); -} - -class _DoctorDetailsScreenState extends State { - bool isDescriptionExpanded = false; - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xFFF5F7FF), - body: Column( - children: [ - _buildAppBar(context), - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildDoctorCard(), - const SizedBox(height: 24), - _buildDescription(), - const SizedBox(height: 24), - _buildQualifications(), - ], - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pushNamed( - context, RouteNames.consultationCenterScreen, - arguments: { - 'doctor': widget.doctor, - }); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - disabledBackgroundColor: Colors.grey[300], - ), - child: Text( - 'Confirm Booking', - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildAppBar(BuildContext context) { - return Container( - color: Colors.white, - child: AppBar( - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black87), - onPressed: () => Navigator.pop(context), - ), - title: Text( - 'Doctor', - style: GoogleFonts.poppins( - color: Colors.black87, - fontWeight: FontWeight.w600, - fontSize: 20, - ), - ), - centerTitle: true, - ), - ); - } - - Widget _buildDoctorCard() { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildDoctorImage(), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.doctor.firstName ?? '', - style: GoogleFonts.poppins( - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - Text( - widget.doctor.speciality!, - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Row( - children: [ - Icon(Icons.medical_services, - size: 16, color: Colors.blue[400]), - const SizedBox(width: 4), - Expanded( - child: Text( - widget.doctor.speciality!, - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ), - ], - ), - Row( - children: [ - Icon(Icons.location_on, - size: 16, color: Colors.blue[400]), - const SizedBox(width: 4), - Expanded( - child: Text( - widget.doctor.city!, - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ), - ], - ), - Row( - children: [ - Icon(Icons.star, size: 16, color: Colors.blue[400]), - const SizedBox(width: 4), - Text( - '${widget.doctor.yearsOfExperience} Years', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ], - ), - ], - ), - ), - ], - ), - ], - ), - ); - } - - Widget _buildDoctorImage() { - final imageProvider = - widget.preloadedImage ?? NetworkImage(widget.doctor.profileImageUrl!); - return ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Image( - image: imageProvider, - width: 100, - height: 100, - fit: BoxFit.cover, - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded || frame != null) { - return child; - } - - return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - ), - ); - }, - errorBuilder: (context, error, stackTrace) { - return Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - Icons.person, - size: 50, - color: Colors.grey[600], - ), - ); - }, - ), - ); - } - - Widget _buildDescription() { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Description', - style: GoogleFonts.poppins( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Text( - widget.doctor.profileDescription!, - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - maxLines: isDescriptionExpanded ? null : 3, - overflow: isDescriptionExpanded ? null : TextOverflow.ellipsis, - ), - Align( - alignment: Alignment.topLeft, - child: TextButton( - onPressed: () { - setState(() { - isDescriptionExpanded = !isDescriptionExpanded; - }); - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - minimumSize: Size.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: Text( - isDescriptionExpanded ? 'Show less' : 'Read more', - style: GoogleFonts.poppins( - color: Colors.blue, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildQualifications() { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Qualifications', - style: GoogleFonts.poppins( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Text( - widget.doctor.qualifications?.join(', ') ?? '', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ], - ), - ); - } -} diff --git a/lib/screens/patient_screens/appoinment_bookings/doctors_list_screen.dart b/lib/screens/patient_screens/appoinment_bookings/doctors_list_screen.dart deleted file mode 100644 index 39ba33f..0000000 --- a/lib/screens/patient_screens/appoinment_bookings/doctors_list_screen.dart +++ /dev/null @@ -1,387 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:medora/data/models/doctor.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:medora/route/route_names.dart'; -import 'package:shimmer/shimmer.dart'; - -class DoctorsListScreen extends StatefulWidget { - final String specialty; - - const DoctorsListScreen({ - super.key, - required this.specialty, - }); - - @override - State createState() => _DoctorsListScreenState(); -} - -class _DoctorsListScreenState extends State { - late final Query doctorsQuery; - final TextEditingController _searchController = TextEditingController(); - bool _isSearching = false; - - @override - void initState() { - super.initState(); - - doctorsQuery = FirebaseFirestore.instance - .collection('doctorprofiles') - .where('speciality', isEqualTo: widget.specialty); - _searchController.addListener(_onSearchChanged); - } - - @override - void dispose() { - _searchController.dispose(); - super.dispose(); - } - - void _onSearchChanged() { - setState(() { - _isSearching = _searchController.text.isNotEmpty; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xFFF5F7FF), - body: CustomScrollView( - slivers: [ - _buildSliverAppBar(), - SliverToBoxAdapter( - child: _buildSearchBar(), - ), - _buildDoctorsList(), - ], - ), - ); - } - - Widget _buildSliverAppBar() { - return SliverAppBar( - expandedHeight: 55, - floating: true, - pinned: true, - stretch: true, - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black87), - onPressed: () => Navigator.pop(context), - ), - flexibleSpace: FlexibleSpaceBar( - title: Text( - '${widget.specialty} Specialists', - style: GoogleFonts.poppins( - color: Colors.black87, - fontWeight: FontWeight.w600, - fontSize: 20, - ), - ), - centerTitle: true, - ), - ); - } - - Widget _buildSearchBar() { - return Padding( - padding: const EdgeInsets.all(16), - child: Container( - 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: TextField( - controller: _searchController, - decoration: InputDecoration( - hintText: 'Search doctors...', - hintStyle: GoogleFonts.poppins( - color: Colors.grey, - fontSize: 14, - ), - prefixIcon: const Icon(Icons.search, color: Colors.grey), - suffixIcon: _isSearching - ? IconButton( - icon: const Icon(Icons.clear), - onPressed: () { - _searchController.clear(); - FocusScope.of(context).unfocus(); - }, - ) - : null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide.none, - ), - filled: true, - fillColor: Colors.white, - ), - ), - ), - ); - } - - Widget _buildDoctorsList() { - return SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 16), - sliver: StreamBuilder( - stream: doctorsQuery.snapshots(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => _buildShimmerDoctorCard(), - ), - ); - } - - if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { - return SliverFillRemaining( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.medical_services_outlined, - size: 64, - color: Colors.grey[400], - ), - const SizedBox(height: 16), - Text( - 'No doctors available in this specialty', - style: GoogleFonts.poppins( - fontSize: 16, - color: Colors.grey[600], - ), - ), - ], - ), - ), - ); - } - - final doctors = snapshot.data!.docs - .map((doc) => Doctor.fromJson(doc.data() as Map)) - .where((doctor) { - if (_searchController.text.isEmpty) return true; - final searchQuery = _searchController.text.toLowerCase(); - return doctor.firstName!.toLowerCase().contains(searchQuery) || - doctor.city!.toLowerCase().contains(searchQuery); - }).toList(); - - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final doctor = doctors[index]; - return _buildDoctorCard(doctor); - }, - childCount: doctors.length, - ), - ); - }, - ), - ); - } - - Widget _buildShimmerDoctorCard() { - return Container( - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 5), - ), - ], - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Row( - children: [ - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 120, - height: 20, - color: Colors.white, - ), - const SizedBox(height: 8), - Container( - width: 150, - height: 16, - color: Colors.white, - ), - const SizedBox(height: 8), - Container( - width: 100, - height: 16, - color: Colors.white, - ), - ], - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildDoctorCard(Doctor doctor) { - return Container( - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 5), - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - precacheImage(NetworkImage(doctor.profileImageUrl!), context); - Navigator.pushNamed( - context, - RouteNames.doctorDetailsScreen, - arguments: { - 'doctor': doctor, - 'imageProvider': NetworkImage(doctor.profileImageUrl!), - }, - ); - }, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - _buildDoctorImage(doctor), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - doctor.firstName ?? '', - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 4), - Text( - '${doctor.yearsOfExperience!} years experience', - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 4), - Row( - children: [ - Icon( - Icons.location_on, - size: 16, - color: Colors.grey[600], - ), - const SizedBox(width: 4), - Expanded( - child: Text( - doctor.city!, - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ], - ), - ), - Icon( - Icons.arrow_forward_ios, - size: 16, - color: Colors.grey[400], - ), - ], - ), - ), - ), - ), - ), - ); - } - - Widget _buildDoctorImage(Doctor doctor) { - return Container( - width: 80, - height: 80, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Image.network( - doctor.profileImageUrl!, - fit: BoxFit.cover, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) return child; - return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Container( - color: Colors.white, - ), - ); - }, - errorBuilder: (context, error, stackTrace) { - return Container( - color: Colors.grey[200], - child: Icon( - Icons.person, - size: 40, - color: Colors.grey[400], - ), - ); - }, - ), - ), - ); - } -} diff --git a/lib/screens/patient_screens/appoinment_bookings/speciality_screen.dart b/lib/screens/patient_screens/appoinment_bookings/speciality_screen.dart deleted file mode 100644 index 233b8a8..0000000 --- a/lib/screens/patient_screens/appoinment_bookings/speciality_screen.dart +++ /dev/null @@ -1,426 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:animate_do/animate_do.dart'; -import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; -import 'package:medora/route/route_names.dart'; - -class Specialty { - final String name; - final IconData icon; - final Color color; - final String description; - - Specialty({ - required this.name, - required this.icon, - required this.color, - required this.description, - }); -} - -class SpecialtyScreen extends StatefulWidget { - const SpecialtyScreen({super.key}); - - @override - State createState() => _SpecialtyScreenState(); -} - -class _SpecialtyScreenState extends State { - final List _allSpecialties = [ - Specialty( - name: 'Pediatric', - icon: Icons.child_care, - color: Colors.blue, - description: 'Medical care for infants, children, and adolescents', - ), - Specialty( - name: 'General Medicine', - icon: Icons.medical_services, - color: Colors.green, - description: - 'Primary healthcare for adults and general medical conditions', - ), - Specialty( - name: 'Family Medicine', - icon: Icons.family_restroom, - color: Colors.teal, - description: 'Comprehensive healthcare for families and individuals', - ), - Specialty( - name: 'Cardiologist', - icon: Icons.favorite, - color: Colors.red, - description: 'Diagnosis and treatment of heart conditions', - ), - Specialty( - name: 'Neurology', - icon: Icons.psychology, - color: Colors.purple, - description: 'Treatment of nervous system disorders', - ), - Specialty( - name: 'Gastroenterology', - icon: Icons.local_hospital, - color: Colors.orange, - description: 'Digestive system disorders and treatment', - ), - Specialty( - name: 'Dermatologist', - icon: Icons.face, - color: Colors.pink, - description: 'Skin, hair, and nail conditions', - ), - Specialty( - name: 'Orthopedic', - icon: Icons.wheelchair_pickup, - color: Colors.indigo, - description: 'Musculoskeletal system and injury treatment', - ), - Specialty( - name: 'Ophthalmology', - icon: Icons.remove_red_eye, - color: Colors.brown, - description: 'Eye care and vision treatment', - ), - Specialty( - name: 'ENT', - icon: Icons.hearing, - color: Colors.cyan, - description: 'Ear, nose, and throat specialist', - ), - Specialty( - name: 'Psychiatry', - icon: Icons.psychology_outlined, - color: Colors.deepPurple, - description: 'Mental health and behavioral disorders', - ), - Specialty( - name: 'Gynecology', - icon: Icons.pregnant_woman, - color: Colors.pinkAccent, - description: "Women's health and reproductive care", - ), - Specialty( - name: 'Urology', - icon: Icons.water_drop, - color: Colors.lightBlue, - description: 'Urinary tract and male reproductive health', - ), - Specialty( - name: 'Endocrinology', - icon: Icons.biotech, - color: Colors.amber, - description: 'Hormone and metabolic disorders', - ), - Specialty( - name: 'Oncology', - icon: Icons.bloodtype, - color: Colors.redAccent, - description: 'Cancer diagnosis and treatment', - ), - Specialty( - name: 'Rheumatology', - icon: Icons.accessibility, - color: Colors.deepOrange, - description: 'Arthritis and autoimmune conditions', - ), - Specialty( - name: 'Pulmonology', - icon: Icons.air, - color: Colors.lightGreen, - description: 'Respiratory system disorders', - ), - Specialty( - name: 'Nephrology', - icon: Icons.water, - color: Colors.blueGrey, - description: 'Kidney diseases and disorders', - ), - Specialty( - name: 'Dentistry', - icon: Icons.cleaning_services, - color: Colors.cyan, - description: 'Oral health and dental care', - ), - Specialty( - name: 'Physical Therapy', - icon: Icons.accessibility_new, - color: Colors.deepPurple, - description: 'Rehabilitation and physical medicine', - ), - Specialty( - name: 'Sports Medicine', - icon: Icons.sports, - color: Colors.green, - description: 'Athletic injuries and performance', - ), - Specialty( - name: 'Allergy & Immunology', - icon: Icons.sick, - color: Colors.orange, - description: 'Allergies and immune system disorders', - ), - Specialty( - name: 'Pain Management', - icon: Icons.healing, - color: Colors.red, - description: 'Chronic pain treatment', - ), - Specialty( - name: 'Sleep Medicine', - icon: Icons.bedtime, - color: Colors.indigo, - description: 'Sleep disorders and treatment', - ), - Specialty( - name: 'Geriatrics', - icon: Icons.elderly, - color: Colors.brown, - description: 'Healthcare for elderly patients', - ), - ]; - - late List _filteredSpecialties; - final TextEditingController _searchController = TextEditingController(); - bool _isSearching = false; - - @override - void initState() { - super.initState(); - _filteredSpecialties = _allSpecialties; - _searchController.addListener(_onSearchChanged); - } - - @override - void dispose() { - _searchController.dispose(); - super.dispose(); - } - - void _onSearchChanged() { - final searchQuery = _searchController.text.toLowerCase(); - setState(() { - _isSearching = searchQuery.isNotEmpty; - _filteredSpecialties = _allSpecialties - .where((specialty) => - specialty.name.toLowerCase().contains(searchQuery) || - specialty.description.toLowerCase().contains(searchQuery)) - .toList(); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xFFF5F7FF), - body: CustomScrollView( - slivers: [ - _buildSliverAppBar(), - SliverToBoxAdapter( - child: _buildSearchBar(), - ), - SliverPadding( - padding: const EdgeInsets.all(16), - sliver: _buildSpecialtiesGrid(), - ), - ], - ), - ); - } - - Widget _buildSliverAppBar() { - return SliverAppBar( - expandedHeight: 55, - floating: true, - pinned: true, - stretch: true, - backgroundColor: Colors.white, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black87), - onPressed: () => Navigator.pop(context), - ), - flexibleSpace: FlexibleSpaceBar( - title: Text( - 'Find a Specialist', - style: GoogleFonts.poppins( - color: Colors.black87, - fontWeight: FontWeight.w600, - fontSize: 20, - ), - ), - centerTitle: true, - ), - ); - } - - Widget _buildSearchBar() { - return FadeIn( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Container( - 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: TextField( - controller: _searchController, - decoration: InputDecoration( - hintText: 'Search specialties...', - hintStyle: GoogleFonts.poppins( - color: Colors.grey, - fontSize: 14, - ), - prefixIcon: const Icon(Icons.search, color: Colors.grey), - suffixIcon: _isSearching - ? IconButton( - icon: const Icon(Icons.clear), - onPressed: () { - _searchController.clear(); - FocusScope.of(context).unfocus(); - }, - ) - : null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide.none, - ), - filled: true, - fillColor: Colors.white, - ), - ), - ), - ), - ); - } - - Widget _buildSpecialtiesGrid() { - return SliverAnimationBuilder( - child: MasonryGridView.count( - crossAxisCount: 2, - mainAxisSpacing: 16, - crossAxisSpacing: 16, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _filteredSpecialties.length, - itemBuilder: (context, index) { - final specialty = _filteredSpecialties[index]; - return FadeInUp( - delay: Duration(milliseconds: 100 * index), - child: _buildSpecialtyCard(specialty), - ); - }, - ), - ); - } - - Widget _buildSpecialtyCard(Specialty specialty) { - return GestureDetector( - onTap: () { - Navigator.pushNamed( - context, - RouteNames.doctorListScreen, - arguments: { - 'specialty': specialty.name, - }, - ); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: specialty.color.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 5), - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: Stack( - children: [ - Positioned( - right: -20, - top: -20, - child: CircleAvatar( - radius: 40, - backgroundColor: specialty.color.withOpacity(0.1), - ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: specialty.color.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - specialty.icon, - color: specialty.color, - size: 28, - ), - ), - const SizedBox(height: 16), - Text( - specialty.name, - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - const SizedBox(height: 8), - Text( - specialty.description, - style: GoogleFonts.poppins( - fontSize: 12, - color: Colors.grey[600], - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} - -class SliverAnimationBuilder extends StatelessWidget { - final Widget child; - - const SliverAnimationBuilder({ - super.key, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return SliverAnimatedList( - initialItemCount: 1, - itemBuilder: (context, index, animation) { - return SlideInUp( - from: 50, - child: child, - ); - }, - ); - } -} diff --git a/lib/screens/patient_screens/patient_dashboard/patient_dashboard_screen.dart b/lib/screens/patient_screens/patient_dashboard/patient_dashboard_screen.dart deleted file mode 100644 index 9b82a9b..0000000 --- a/lib/screens/patient_screens/patient_dashboard/patient_dashboard_screen.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:animations/animations.dart'; -import 'package:curved_navigation_bar/curved_navigation_bar.dart'; -import 'package:medora/screens/patient_screens/patient_dashboard/patient_home_screen.dart'; -import 'package:medora/screens/patient_screens/patient_dashboard/patient_profile_screen.dart'; - -class PatientDashboardScreen extends StatefulWidget { - const PatientDashboardScreen({super.key}); - - @override - State createState() => _PatientDashboardScreenState(); -} - -class _PatientDashboardScreenState extends State { - int _selectedIndex = 0; - final GlobalKey _bottomNavigationKey = GlobalKey(); - - // Add your pages here - final List _pages = [ - const PatientHomeScreen(), - const Center(child: Text('Chat')), // Replace with your chat screen - const Center(child: Text('Records')), // Replace with your records screen - const PatientProfileScreen(), - ]; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: PageTransitionSwitcher( - duration: const Duration(milliseconds: 300), - transitionBuilder: (child, animation, secondaryAnimation) { - return FadeThroughTransition( - animation: animation, - secondaryAnimation: secondaryAnimation, - child: child, - ); - }, - child: _pages[_selectedIndex], - ), - bottomNavigationBar: CurvedNavigationBar( - key: _bottomNavigationKey, - backgroundColor: Colors.transparent, - color: Colors.blue, - buttonBackgroundColor: Colors.blue, - height: 60, - index: _selectedIndex, - items: const [ - Icon(Icons.home, size: 30, color: Colors.white), - Icon(Icons.chat_bubble, size: 30, color: Colors.white), - Icon(Icons.assignment, size: 30, color: Colors.white), - Icon(Icons.person, size: 30, color: Colors.white), - ], - onTap: (index) { - setState(() { - _selectedIndex = index; - }); - }, - ), - ); - } -} diff --git a/lib/screens/patient_screens/patient_dashboard/patient_home_screen.dart b/lib/screens/patient_screens/patient_dashboard/patient_home_screen.dart deleted file mode 100644 index 165b283..0000000 --- a/lib/screens/patient_screens/patient_dashboard/patient_home_screen.dart +++ /dev/null @@ -1,695 +0,0 @@ -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_booking.dart'; -import 'package:medora/data/services/consultation_booking_service.dart'; -import 'package:medora/route/route_names.dart'; -import 'package:medora/screens/patient_screens/appoinment_bookings/speciality_screen.dart'; - -class PatientHomeScreen extends StatefulWidget { - const PatientHomeScreen({super.key}); - - @override - State createState() => _PatientHomeScreenState(); -} - -class _PatientHomeScreenState extends State - with SingleTickerProviderStateMixin { - late AnimationController _animationController; - final BookingService _bookingService = BookingService(); - late Stream> _bookingsStream; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 300), - ); - _animationController.forward(); - - final User? user = FirebaseAuth.instance.currentUser; - if (user != null) { - final String userId = user.uid; - _bookingsStream = _bookingService.getPatientBookings(userId); - } else { - _bookingsStream = const Stream.empty(); - } - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Column( - children: [ - _buildSearchBar(), - Expanded( - child: ListView( - padding: const EdgeInsets.all(16), - children: [ - _buildRealTimeCard(), - const SizedBox(height: 20), - _buildConsultationsSection(), - const SizedBox(height: 20), - _buildFindDoctorSection(), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildSearchBar() { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(30.0), - bottomRight: Radius.circular(30.0), - ), - boxShadow: [ - BoxShadow( - color: Colors.blue.withOpacity(0.3), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(30), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: TextField( - decoration: InputDecoration( - hintText: 'Search Doctor/Hospital/Symptoms', - hintStyle: GoogleFonts.poppins( - color: Colors.grey[400], - ), - prefixIcon: const Icon(Icons.search, color: Colors.blue), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(30), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 20), - ), - ), - ), - ], - ), - ); - } - - Widget _buildRealTimeCard() { - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue[400]!, Colors.white], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.blue.withOpacity(0.3), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Real-time care\nat your fingertips.', - style: GoogleFonts.poppins( - fontSize: 30, - fontWeight: FontWeight.bold, - color: const Color.fromARGB(221, 67, 67, 67), - ), - ), - const SizedBox(height: 12), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SpecialtyScreen()), - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.blue[700], - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 7), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - elevation: 5, - ), - child: Text( - 'Start Consultation', - style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ); - } - - Widget _buildConsultationsSection() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Upcoming Consultations', - style: GoogleFonts.poppins( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.grey[800], - ), - ), - ], - ), - ), - const SizedBox(height: 20), - SizedBox( - height: 201, - child: StreamBuilder>( - stream: _bookingsStream, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 12), - Text( - 'Loading consultations...', - style: GoogleFonts.poppins( - color: Colors.grey[600], - ), - ), - ], - ), - ); - } - - if (snapshot.hasError) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.error_outline, - color: Colors.red[400], size: 48), - const SizedBox(height: 12), - Text( - 'Error loading consultations', - style: GoogleFonts.poppins( - color: Colors.red[400], - fontWeight: FontWeight.w500, - ), - ), - TextButton( - onPressed: () { - // Implement refresh logic - }, - child: Text( - 'Try Again', - style: GoogleFonts.poppins( - color: Colors.blue[700], - ), - ), - ), - ], - ), - ); - } - - final bookings = snapshot.data ?? []; - - if (bookings.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.calendar_today, - color: Colors.grey[400], size: 48), - const SizedBox(height: 12), - Text( - 'No upcoming consultations', - style: GoogleFonts.poppins( - color: Colors.grey[600], - fontSize: 16, - ), - ), - TextButton( - onPressed: () { - // Navigate to book consultation - }, - child: Text( - 'Book a Consultation', - style: GoogleFonts.poppins( - color: Colors.blue[700], - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - ); - } - - return ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 20), - scrollDirection: Axis.horizontal, - itemCount: bookings.length, - itemBuilder: (context, index) { - final booking = bookings[index]; - return Padding( - padding: const EdgeInsets.only(right: 16), - child: Hero( - tag: 'consultation_${booking.id}', - child: Material( - child: _consultationCard( - booking.profileImageUrl, - booking.doctorName, - '${DateFormat('EEE, MMM d, yyyy').format(booking.appointmentDate)}\n${booking.appointmentTime}', - booking.specialization, - booking.paymentStatus, - ), - ), - ), - ); - }, - ); - }, - ), - ), - ], - ); - } - - Widget _consultationCard( - String? profileImageUrl, - String name, - String schedule, - String speciality, - PaymentStatus paymentStatus, - ) { - return Container( - width: 300, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.white, Colors.grey[50]!], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(24), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (profileImageUrl != null) - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - image: NetworkImage(profileImageUrl), - fit: BoxFit.cover, - ), - boxShadow: [ - BoxShadow( - color: Colors.blue[300]!.withOpacity(0.3), - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], - ), - ) - else - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue[400]!, Colors.blue[600]!], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.blue[300]!.withOpacity(0.3), - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], - ), - child: const Icon( - Icons.person, - size: 36, - color: Colors.white, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: GoogleFonts.poppins( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[800], - ), - ), - Text( - speciality, - style: GoogleFonts.poppins( - color: Colors.grey[600], - fontSize: 14, - ), - ), - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - decoration: BoxDecoration( - color: _getStatusColor(paymentStatus).withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: - _getStatusColor(paymentStatus).withOpacity(0.2), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - _getStatusIcon(paymentStatus), - size: 14, - color: _getStatusColor(paymentStatus), - ), - const SizedBox(width: 4), - Text( - _getStatusText(paymentStatus), - style: GoogleFonts.poppins( - fontSize: 12, - color: _getStatusColor(paymentStatus), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: Colors.blue[50], - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Colors.blue[100]!, - ), - ), - child: Row( - children: [ - Icon( - Icons.calendar_today, - size: 18, - color: Colors.blue[700], - ), - const SizedBox(width: 8), - Text( - schedule, - style: GoogleFonts.poppins( - color: Colors.blue[700], - fontSize: 13, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ], - ), - ); - } - - IconData _getStatusIcon(PaymentStatus status) { - switch (status) { - case PaymentStatus.completed: - return Icons.check_circle; - case PaymentStatus.pending: - return Icons.access_time; - case PaymentStatus.failed: - return Icons.error; - default: - return Icons.info; - } - } - - Color _getStatusColor(PaymentStatus status) { - switch (status) { - case PaymentStatus.pending: - return Colors.orange; - case PaymentStatus.completed: - return Colors.green; - case PaymentStatus.failed: - return Colors.red; - default: - return Colors.grey; - } - } - - String _getStatusText(PaymentStatus status) { - switch (status) { - case PaymentStatus.pending: - return 'Payment Pending'; - case PaymentStatus.completed: - return 'Confirmed'; - case PaymentStatus.failed: - return 'Payment Failed'; - default: - return 'Unknown'; - } - } - - Widget _buildFindDoctorSection() { - final specialistData = [ - { - 'icon': Icons.local_hospital, - 'label': 'General', - 'color': Colors.blue, - 'description': 'Primary Healthcare' - }, - { - 'icon': Icons.remove_red_eye, - 'label': 'Eye', - 'color': Colors.indigo, - 'description': 'Vision Care' - }, - { - 'icon': Icons.medical_services, - 'label': 'Dental', - 'color': Colors.amber, - 'description': 'Oral Health' - }, - { - 'icon': Icons.favorite, - 'label': 'Cardio', - 'color': Colors.red, - 'description': 'Heart Specialist' - }, - { - 'icon': Icons.psychology, - 'label': 'Mental', - 'color': Colors.green, - 'description': 'Mental Health' - }, - { - 'icon': Icons.child_care, - 'label': 'Pediatric', - 'color': Colors.purple, - 'description': 'Child Care' - }, - { - 'icon': Icons.elderly, - 'label': 'Geriatric', - 'color': Colors.teal, - 'description': 'Senior Care' - }, - { - 'icon': Icons.fitness_center, - 'label': 'Physio', - 'color': Colors.orange, - 'description': 'Physical Therapy' - }, - ]; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Text( - 'Find Specialists', - style: GoogleFonts.poppins( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - for (final data in specialistData) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: _specialistCard( - icon: data['icon'] as IconData, - label: data['label'] as String, - color: data['color'] as Color, - description: data['description'] as String, - ), - ), - const SizedBox(width: 8), - ], - ), - ), - ), - IconButton( - icon: const Icon(Icons.arrow_forward, color: Colors.blue), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SpecialtyScreen()), - ); - }, - ), - ], - ), - ], - ); - } - - Widget _specialistCard({ - required IconData icon, - required String label, - required Color color, - required String description, - }) { - return GestureDetector( - onTap: () { - Navigator.pushNamed( - context, - RouteNames.doctorListScreen, - arguments: { - 'specialty': label, - }, - ); - }, - child: Container( - width: 140, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: color.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon(icon, color: color, size: 24), - ), - const SizedBox(height: 8), - Text( - label, - style: GoogleFonts.poppins( - color: Colors.black87, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - Text( - description, - style: GoogleFonts.poppins( - color: Colors.grey[600], - fontSize: 12, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/screens/patient_screens/patient_dashboard/patient_profile_screen.dart b/lib/screens/patient_screens/patient_dashboard/patient_profile_screen.dart deleted file mode 100644 index 703f866..0000000 --- a/lib/screens/patient_screens/patient_dashboard/patient_profile_screen.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:medora/data/services/patient_registration_service.dart'; -import 'package:medora/route/route_names.dart'; -import 'package:medora/data/models/patient.dart'; - -class PatientProfileScreen extends StatefulWidget { - const PatientProfileScreen({super.key}); - - @override - State createState() => _PatientProfileScreenState(); -} - -class _PatientProfileScreenState extends State { - final FirebaseAuth _auth = FirebaseAuth.instance; - PatientModel? _patientProfile; - - @override - void initState() { - super.initState(); - _fetchPatientProfile(); - } - - Future _fetchPatientProfile() async { - final patientProfile = await PatientProfileService.getPatientProfile(); - if (mounted) { - setState(() { - _patientProfile = patientProfile; - }); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: Column( - children: [ - _buildProfileHeader(), - _buildProfileOptions(), - ], - ), - ), - ); - } - - Widget _buildProfileHeader() { - return Container( - padding: const EdgeInsets.all(16), - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [ - Color(0xFF00BCD4), - Color(0xFF2196F3), - ], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20), - ), - ), - child: Row( - children: [ - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - image: _patientProfile?.profileImageUrl != null - ? DecorationImage( - image: NetworkImage(_patientProfile!.profileImageUrl!), - fit: BoxFit.cover, - ) - : null, - ), - child: _patientProfile?.profileImageUrl == null - ? Center( - child: Text( - _patientProfile != null && _patientProfile!.name != null - ? _patientProfile!.name![0].toUpperCase() - : '', - style: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: Colors.blue, - ), - ), - ) - : null, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _patientProfile != null && _patientProfile!.name != null - ? _patientProfile!.name! - : 'Create your profile', - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ], - ), - ), - const Icon( - Icons.chevron_right, - color: Colors.white, - size: 30, - ), - ], - ), - ); - } - - Widget _buildProfileOptions() { - return Container( - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - spreadRadius: 1, - blurRadius: 5, - ), - ], - ), - child: Column( - children: [ - _buildOptionTile( - 'Medical Profile', - Icons.medical_information_outlined, - onTap: () { - // Add navigation or action - }, - ), - const Divider(height: 1), - _buildOptionTile( - 'Sign Out', - Icons.logout, - onTap: () { - _signOut(); - }, - iconColor: Colors.blue, - ), - ], - ), - ); - } - - Widget _buildOptionTile(String title, IconData icon, - {required VoidCallback onTap, Color? iconColor}) { - return ListTile( - leading: Icon( - icon, - color: iconColor ?? Colors.grey, - size: 24, - ), - title: Text( - title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - trailing: const Icon( - Icons.chevron_right, - color: Colors.grey, - ), - onTap: onTap, - ); - } - - Future _signOut() async { - try { - await _auth.signOut(); - if (mounted) { - Navigator.of(context).pushReplacementNamed(RouteNames.launch); - } - } catch (e) { - print("Error signing out: $e"); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Failed to log out. Please try again.')), - ); - } - } - } -} diff --git a/lib/screens/patient_screens/patient_landing_screen.dart b/lib/screens/patient_screens/patient_landing_screen.dart deleted file mode 100644 index f3cda12..0000000 --- a/lib/screens/patient_screens/patient_landing_screen.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:medora/route/route_names.dart'; -import 'package:flutter/material.dart'; - -class PatientLandingScreen extends StatelessWidget { - const PatientLandingScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.teal.shade100, Colors.white], - ), - ), - child: SafeArea( - child: Column( - children: [ - Expanded( - child: Center( - child: Card( - margin: const EdgeInsets.symmetric(horizontal: 32), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Align( - alignment: Alignment.topRight, - child: TextButton( - onPressed: () { - Navigator.of(context).pushNamed( - RouteNames.patientDashboardScreen); - }, - child: Text( - 'Skip', - style: TextStyle( - color: Colors.teal.shade300, - fontSize: 16, - fontWeight: FontWeight.bold), - ), - ), - ), - Image.asset( - 'images/patient-avathar.png', - height: 200, - width: 200, - ), - const SizedBox(height: 24), - const Text( - 'Set your medical profile', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () { - Navigator.of(context).pushNamed( - RouteNames.patientRegistrationScreen); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - minimumSize: const Size(double.infinity, 50), - ), - child: const Text( - 'Continue', - style: - TextStyle(fontSize: 18, color: Colors.white), - ), - ), - ], - ), - ), - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/screens/patient_screens/registration_screens/family_members_edit_screen.dart b/lib/screens/patient_screens/registration_screens/family_members_edit_screen.dart deleted file mode 100644 index 1692af9..0000000 --- a/lib/screens/patient_screens/registration_screens/family_members_edit_screen.dart +++ /dev/null @@ -1,285 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:medora/controllers/patient_controller.dart'; -import 'package:medora/data/models/patient.dart'; - -class FamilyMembersEditScreen extends StatefulWidget { - final FamilyMember? familyMember; - final PatientController controller; - - const FamilyMembersEditScreen( - {super.key, this.familyMember, required this.controller}); - - @override - State createState() => - _FamilyMembersEditScreenState(); -} - -class _FamilyMembersEditScreenState extends State { - late TextEditingController nameController; - late TextEditingController relationController; - late TextEditingController genderController; - late TextEditingController dobController; - Map errors = {}; - - @override - void initState() { - super.initState(); - nameController = - TextEditingController(text: widget.familyMember?.name ?? ''); - relationController = - TextEditingController(text: widget.familyMember?.relation ?? ''); - genderController = - TextEditingController(text: widget.familyMember?.gender ?? ''); - dobController = TextEditingController( - text: widget.familyMember?.dateOfBirth?.toString().split(' ')[0] ?? ''); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Edit Family Member'), - actions: _buildAppBarActions(), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTextField(nameController, 'Name', Icons.person, 'name'), - _buildDropdownField( - 'Relation', - relationController.text, - (String? newValue) { - setState(() { - relationController.text = newValue ?? ''; - }); - }, - Icons.family_restroom, - ), - _buildDropdownField( - 'Gender', - genderController.text, - (String? newValue) { - setState(() { - genderController.text = newValue ?? ''; - }); - }, - Icons.transgender, - ), - _buildDateField(context), - ], - ), - ), - ); - } - - Widget _buildDropdownField( - String label, String value, Function(String?) onChanged, IconData icon) { - return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: DropdownButtonFormField( - decoration: InputDecoration( - labelText: label, - prefixIcon: Icon(icon, color: Colors.blue), - border: const OutlineInputBorder(), - ), - value: value.isEmpty ? null : value, - onChanged: onChanged, - items: label == 'Relation' - ? ['Father', 'Mother', 'Son', 'Daughter', 'Other'] - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList() - : ['Male', 'Female', 'Other'] - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - ); - } - - Widget _buildDateField(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: TextField( - controller: dobController, - decoration: const InputDecoration( - labelText: 'Date of Birth', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.calendar_today, color: Colors.blue), - ), - readOnly: true, - onTap: () async { - DateTime? pickedDate = await showDatePicker( - context: context, - initialDate: DateTime.now().subtract(const Duration(days: 365)), - firstDate: DateTime(1900), - lastDate: DateTime.now().subtract(const Duration(days: 365)), - ); - if (pickedDate != null) { - setState(() { - dobController.text = pickedDate.toString().split(' ')[0]; - }); - } - }, - ), - ); - } - - bool _validateFields() { - errors.clear(); - - if (nameController.text.trim().isEmpty) { - errors['name'] = 'Name is required'; - } else if (nameController.text.trim().length < 2) { - errors['name'] = 'Name must be at least 2 characters'; - } - - if (relationController.text.isEmpty) { - errors['relation'] = 'Please select a relation'; - } - - if (genderController.text.isEmpty) { - errors['gender'] = 'Please select a gender'; - } - - if (dobController.text.isEmpty) { - errors['dob'] = 'Date of Birth is required'; - } else { - final dob = DateTime.tryParse(dobController.text); - if (dob == null) { - errors['dob'] = 'Invalid date format'; - } else if (dob.isAfter(DateTime.now())) { - errors['dob'] = 'Date of Birth cannot be in the future'; - } - } - - setState(() {}); - return errors.isEmpty; - } - - void _showValidationErrors() { - 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'), - ), - ], - ), - ); - } - - Widget _buildTextField( - TextEditingController controller, - String label, - IconData icon, - String errorKey, - ) { - return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextField( - controller: controller, - decoration: InputDecoration( - labelText: label, - prefixIcon: Icon( - icon, - color: errors.containsKey(errorKey) ? Colors.red : Colors.blue, - ), - border: OutlineInputBorder( - borderSide: BorderSide( - color: - errors.containsKey(errorKey) ? Colors.red : Colors.grey, - ), - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: - errors.containsKey(errorKey) ? Colors.red : Colors.grey, - ), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: - errors.containsKey(errorKey) ? Colors.red : Colors.blue, - ), - ), - ), - ), - if (errors.containsKey(errorKey)) - Padding( - padding: const EdgeInsets.only(top: 4, left: 12), - child: Text( - errors[errorKey]!, - style: const TextStyle(color: Colors.red, fontSize: 12), - ), - ), - ], - ), - ); - } - - List _buildAppBarActions() { - return [ - TextButton( - onPressed: () { - if (_validateFields()) { - FamilyMember newMember = FamilyMember( - name: nameController.text, - relation: relationController.text, - gender: genderController.text, - dateOfBirth: DateTime.tryParse(dobController.text), - ); - Navigator.pop(context, newMember); - } else { - _showValidationErrors(); - } - }, - child: const Text('Done', style: TextStyle(color: Colors.blue)), - ), - ]; - } - - @override - void dispose() { - nameController.dispose(); - relationController.dispose(); - genderController.dispose(); - dobController.dispose(); - super.dispose(); - } -} diff --git a/lib/screens/patient_screens/registration_screens/patient_adress_screen.dart b/lib/screens/patient_screens/registration_screens/patient_adress_screen.dart deleted file mode 100644 index 9b9f877..0000000 --- a/lib/screens/patient_screens/registration_screens/patient_adress_screen.dart +++ /dev/null @@ -1,381 +0,0 @@ -import 'package:medora/controllers/patient_controller.dart'; -import 'package:flutter/material.dart'; -import 'package:country_state_city_picker/country_state_city_picker.dart'; - -class PatientAddressScreen extends StatefulWidget { - final PatientController? controller; - - const PatientAddressScreen({super.key, required this.controller}); - - @override - State createState() => _PatientAddressScreenState(); -} - -class _PatientAddressScreenState extends State { - 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; - final Map _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(); - } -} diff --git a/lib/screens/patient_screens/registration_screens/patient_family_members_screen.dart b/lib/screens/patient_screens/registration_screens/patient_family_members_screen.dart deleted file mode 100644 index 8798934..0000000 --- a/lib/screens/patient_screens/registration_screens/patient_family_members_screen.dart +++ /dev/null @@ -1,308 +0,0 @@ -import 'package:medora/data/models/patient.dart'; -import 'package:flutter/material.dart'; -import 'package:medora/screens/patient_screens/registration_screens/family_members_edit_screen.dart'; -import '../../../controllers/patient_controller.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; - -class PatientFamilyMembersScreen extends StatefulWidget { - final PatientController controller; - const PatientFamilyMembersScreen({ - super.key, - required this.controller, - }); - - @override - State createState() => - _PatientFamilyMembersScreenState(); -} - -class _PatientFamilyMembersScreenState - extends State { - bool isLoading = false; - final int maxFamilyMembers = 5; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text( - 'Family Members', - style: TextStyle(fontSize: 20), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.black), - onPressed: () { - if (_validateFamilyMembers()) { - Navigator.pop(context); - } - }, - ), - actions: _buildAppBarActions(), - elevation: 0, - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - const SizedBox(height: 8), - Expanded( - child: ListView.builder( - itemCount: widget.controller.model.familyMembers.length, - itemBuilder: (context, index) { - return FamilyMemberCard( - familyMember: widget.controller.model.familyMembers[index], - onEdit: () => _editFamilyMember(index), - onDelete: () => _deleteFamilyMember(index), - ); - }, - ), - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _addFamilyMember, - backgroundColor: Colors.blue, - child: const Icon(Icons.add, color: Colors.white), - ), - ); - } - - List _buildAppBarActions() { - return [ - TextButton( - onPressed: () { - if (_validateFamilyMembers()) { - Navigator.pop(context); - } - }, - child: const Text( - 'Done', - style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold), - ), - ), - ]; - } - - bool _validateFamilyMembers() { - if (widget.controller.model.familyMembers.isEmpty) { - _showValidationError('Please add at least one family member'); - return false; - } - - final relations = widget.controller.model.familyMembers - .map((member) => member.relation?.toLowerCase()) - .toList(); - if (relations.toSet().length != relations.length) { - _showValidationError('Duplicate relations are not allowed'); - return false; - } - - return true; - } - - void _showValidationError(String message) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Row( - children: [ - Icon(Icons.error_outline, color: Colors.red), - SizedBox(width: 8), - Text('Validation Error'), - ], - ), - content: Text(message), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); - } - - void _addFamilyMember() { - if (widget.controller.model.familyMembers.length >= maxFamilyMembers) { - _showValidationError('Maximum $maxFamilyMembers family members allowed'); - return; - } - - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FamilyMembersEditScreen( - controller: widget.controller, - ), - ), - ).then((newMember) { - if (newMember != null) { - setState(() { - widget.controller.addFamilyMember(newMember); - }); - } - }); - } - - void _editFamilyMember(int index) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FamilyMembersEditScreen( - controller: widget.controller, - familyMember: widget.controller.model.familyMembers[index], - ), - ), - ).then((editedMember) { - if (editedMember != null) { - setState(() { - widget.controller.updateFamilyMember(index, editedMember); - }); - } - }); - } - - void _deleteFamilyMember(int index) { - if (widget.controller.model.familyMembers.length <= 1) { - _showValidationError('At least one family member is required'); - return; - } - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Delete Family Member'), - content: - const Text('Are you sure you want to delete this family member?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - setState(() { - widget.controller.deleteFamilyMember(index); - }); - Navigator.pop(context); - }, - child: const Text('Delete', style: TextStyle(color: Colors.red)), - ), - ], - ), - ); - } -} - -class FamilyMemberCard extends StatelessWidget { - final FamilyMember familyMember; - final VoidCallback onEdit; - final VoidCallback onDelete; - - const FamilyMemberCard({ - super.key, - required this.familyMember, - required this.onEdit, - required this.onDelete, - }); - - Widget _buildInfoRow(IconData icon, String label, String? value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(icon, size: 20, color: Colors.blueGrey), - const SizedBox(width: 6), - Text( - label, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: Colors.blueGrey, - ), - ), - const SizedBox(width: 6), - Expanded( - child: Text( - value ?? 'Not provided', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - color: value == null || value.isEmpty - ? Colors.redAccent - : Colors.black87, - fontStyle: value == null || value.isEmpty - ? FontStyle.italic - : FontStyle.normal, - ), - ), - ), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - return Slidable( - key: ValueKey(familyMember), - endActionPane: ActionPane( - motion: const ScrollMotion(), - extentRatio: 0.3, - children: [ - SlidableAction( - onPressed: (context) => onEdit(), - foregroundColor: Colors.blue, - icon: Icons.edit, - padding: EdgeInsets.zero, - spacing: 0, - ), - SlidableAction( - onPressed: (context) => onDelete(), - foregroundColor: Colors.red, - icon: Icons.delete, - padding: EdgeInsets.zero, - spacing: 0, - ), - ], - ), - child: Card( - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.blueGrey[50], - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - _buildInfoRow(Icons.person, 'Name:', familyMember.name), - const SizedBox(height: 10), - _buildInfoRow( - Icons.transgender, 'Gender:', familyMember.gender), - const SizedBox(height: 10), - _buildInfoRow( - Icons.cake, - 'Date of Birth:', - familyMember.dateOfBirth?.toString().split(' ')[0] ?? - 'Not provided', - ), - const SizedBox(height: 10), - _buildInfoRow( - Icons.family_restroom, 'Relation:', familyMember.relation), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/screens/patient_screens/registration_screens/patient_registration_screen.dart b/lib/screens/patient_screens/registration_screens/patient_registration_screen.dart deleted file mode 100644 index 2b2af16..0000000 --- a/lib/screens/patient_screens/registration_screens/patient_registration_screen.dart +++ /dev/null @@ -1,680 +0,0 @@ -import 'package:medora/route/route_names.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:image_picker/image_picker.dart'; - -import 'dart:io'; -import '../../../controllers/patient_controller.dart'; -import '../../../widgets/alert_screen.dart'; - -class PatientRegistrationScreen extends StatefulWidget { - const PatientRegistrationScreen({super.key}); - - @override - State createState() => - _PatientRegistrationScreenState(); -} - -class _PatientRegistrationScreenState extends State { - final PatientController _controller = PatientController(); - final TextEditingController _nameController = TextEditingController(); - final TextEditingController _phoneController = TextEditingController(); - bool _hasErrors = false; - final Map _errors = {}; - - String? _gender; - DateTime? _dateOfBirth; - File? _image; - final ImagePicker _picker = ImagePicker(); - String _selectedCountryCode = '+1'; - - final List _countryCodes = ['+1', '+91', '+44', '+61', '+81']; - @override - void initState() { - super.initState(); - _nameController.text = _controller.model.name ?? ''; - if (_controller.model.phoneNumber != null) { - String phoneNumber = _controller.model.phoneNumber!; - if (phoneNumber.startsWith('+')) { - for (String code in _countryCodes) { - if (phoneNumber.startsWith(code)) { - _selectedCountryCode = code; - _phoneController.text = phoneNumber.substring(code.length); - break; - } - } - } else { - _phoneController.text = phoneNumber; - } - } - - _gender = _controller.model.gender; - _dateOfBirth = _controller.model.dateOfBirth; - if (_controller.model.profileImagePath != null) { - _image = File(_controller.model.profileImagePath!); - } - _updateCombinedPhoneNumber(_phoneController.text); - } - - Future _getImage(ImageSource source) async { - final XFile? pickedFile = await _picker.pickImage(source: source); - - if (pickedFile != null) { - setState(() { - _image = File(pickedFile.path); - }); - _controller.updateProfileImage(pickedFile.path); - } - } - - void _updateCombinedPhoneNumber(String phoneNumber) { - String cleanPhoneNumber = phoneNumber.replaceAll(RegExp(r'^\+\d{1,3}'), ''); - String fullPhoneNumber = '$_selectedCountryCode$cleanPhoneNumber'; - _controller.updatePhoneNumber(fullPhoneNumber); - } - - void _showImageSourceActionSheet(BuildContext context) { - showModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), - child: SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Padding( - padding: EdgeInsets.symmetric(vertical: 16), - child: Text( - 'Select Image Source', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - ListTile( - leading: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon(Icons.photo_library, color: Colors.blue), - ), - title: const Text('Choose from Gallery'), - onTap: () { - _getImage(ImageSource.gallery); - Navigator.pop(context); - }, - ), - ListTile( - leading: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon(Icons.photo_camera, color: Colors.blue), - ), - title: const Text('Take a Photo'), - onTap: () { - _getImage(ImageSource.camera); - Navigator.pop(context); - }, - ), - const SizedBox(height: 16), - ], - ), - ), - ); - }, - ); - } - - void _showResultDialog(bool isSuccess) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AlertScreen( - arguments: AlertArguments( - title: isSuccess ? 'Thank You' : 'Oops!', - message: isSuccess - ? 'Profile created successfully!' - : 'Failed to create profile. Please try again.', - actionTitle: isSuccess ? 'Go to Dashboard' : 'Try Again', - type: isSuccess ? AlertType.success : AlertType.error, - onActionPressed: () { - Navigator.pop(context); - if (isSuccess) { - Navigator.pushReplacementNamed( - context, - RouteNames.patientDashboardScreen, - ); - } - }, - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.grey[50], - appBar: AppBar( - elevation: 0, - backgroundColor: Colors.white, - title: const Text( - 'Create Profile', - style: TextStyle(color: Colors.black), - ), - actions: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: IconButton( - onPressed: () { - if (_validateAllFields()) { - _controller.savePatientData(); - _showResultDialog(true); - } else { - _showValidationErrors(); - } - }, - icon: const Icon(Icons.check, color: Colors.blue, weight: 50), - ), - ), - ], - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - color: Colors.white, - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - GestureDetector( - onTap: () => _showImageSourceActionSheet(context), - child: Stack( - alignment: Alignment.bottomRight, - children: [ - Container( - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.blueGrey.withOpacity(0.5), - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], - shape: BoxShape.circle, - border: Border.all(color: Colors.blue, width: 2), - ), - child: CircleAvatar( - radius: 75, - backgroundImage: - _image != null ? FileImage(_image!) : null, - child: _image == null - ? const Icon(Icons.person, - size: 50, color: Colors.blue) - : null, - ), - ), - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.blueGrey.withOpacity(0.5), - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], - color: Colors.blue, - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2), - ), - child: const Icon(Icons.camera_alt, - size: 20, color: Colors.white), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 8), - Container( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.blueGrey.withOpacity(0.5), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildUniformField( - label: 'Name', - icon: Icons.person_outline, - child: TextField( - controller: _nameController, - onChanged: (value) => _controller.updateName(value), - decoration: const InputDecoration( - border: InputBorder.none, - hintText: 'Enter your name', - ), - ), - ), - _buildUniformField( - label: 'Phone Number', - icon: Icons.phone_outlined, - child: Row( - children: [ - DropdownButtonHideUnderline( - child: DropdownButton( - value: _selectedCountryCode, - onChanged: (String? newValue) { - if (newValue != null) { - setState(() { - _selectedCountryCode = newValue; - }); - - _updateCombinedPhoneNumber( - _phoneController.text); - } - }, - items: - _countryCodes.map>( - (String code) { - return DropdownMenuItem( - value: code, - child: Text(code), - ); - }, - ).toList(), - ), - ), - const SizedBox(width: 12), - Expanded( - child: TextField( - controller: _phoneController, - onChanged: (value) { - _updateCombinedPhoneNumber(value); - }, - keyboardType: TextInputType.phone, - decoration: const InputDecoration( - border: InputBorder.none, - hintText: 'Enter your phone number', - ), - ), - ), - ], - ), - ), - _buildUniformField( - label: 'Gender', - icon: Icons.people_outline, - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: _gender, - isExpanded: true, - hint: const Text('Select gender'), - onChanged: (value) { - setState(() => _gender = value); - _controller.updateGender(value!); - }, - items: ['Male', 'Female', 'Other'] - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - ), - ), - _buildUniformField( - label: 'Date of Birth', - icon: Icons.calendar_today_outlined, - child: InkWell( - onTap: () async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: _dateOfBirth ?? - DateTime.now() - .subtract(const Duration(days: 365 * 18)), - firstDate: DateTime(1900), - lastDate: DateTime.now() - .subtract(const Duration(days: 365 * 18)), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: Colors.blue), - ), - child: child!, - ); - }, - ); - if (picked != null && picked != _dateOfBirth) { - setState(() => _dateOfBirth = picked); - _controller.updateDateOfBirth(picked); - } - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Text( - _dateOfBirth != null - ? DateFormat('dd/MM/yyyy').format(_dateOfBirth!) - : 'Select date of birth', - style: TextStyle( - color: _dateOfBirth != null - ? Colors.black87 - : Colors.grey, - ), - ), - ), - ), - ), - ], - ), - ), - ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.blueGrey.withOpacity(0.5), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - children: [ - _buildNavigationField( - 'Address', - Icons.location_on, - () async { - final result = await Navigator.pushNamed( - context, - RouteNames.patientAdressScreen, - arguments: _controller, - ); - if (result == true) { - setState(() {}); - } - }, - ), - const Divider(height: 1), - _buildNavigationField( - 'Family Members', - Icons.family_restroom_outlined, - () => Navigator.pushNamed( - context, - RouteNames.patientFamilyMembersScreen, - arguments: _controller, - ), - ), - ], - )), - ], - ), - ), - ); - } - - Widget _buildUniformField({ - required String label, - required IconData icon, - required Widget child, - String? errorKey, - }) { - return Container( - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: _errors.containsKey(errorKey ?? '') - ? Colors.red - : Colors.grey.shade200, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16, top: 8), - child: Text( - label, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: _errors.containsKey(errorKey ?? '') - ? Colors.red - : Colors.grey[600], - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8), - child: Row( - children: [ - Icon( - icon, - size: 20, - color: _errors.containsKey(errorKey ?? '') - ? Colors.red - : Colors.blue, - ), - const SizedBox(width: 12), - Expanded(child: child), - ], - ), - ), - if (_errors.containsKey(errorKey ?? '')) - Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8), - child: Text( - _errors[errorKey]!, - style: const TextStyle( - color: Colors.red, - fontSize: 12, - ), - ), - ), - ], - ), - ); - } - - bool _validateAllFields() { - setState(() { - _errors.clear(); - _hasErrors = false; - - final name = _nameController.text.trim(); - if (name.isEmpty) { - _errors['name'] = 'Name is required'; - _hasErrors = true; - } else if (name.length < 2 && - RegExp(r'^[A-Za-z]+([.\s]?[A-Za-z]+)*$').hasMatch(name)) { - _errors['name'] = 'Name must be at least 2 characters'; - _hasErrors = true; - } - - final phoneNumber = _phoneController.text.trim(); - if (phoneNumber.isEmpty) { - _errors['phone'] = 'Phone number is required'; - _hasErrors = true; - } else if (!RegExp(r'^\d{10}$').hasMatch(phoneNumber)) { - _errors['phone'] = 'Enter a valid 10-digit phone number'; - _hasErrors = true; - } - - if (_gender == null) { - _errors['gender'] = 'Please select a gender'; - _hasErrors = true; - } - - if (_dateOfBirth == null) { - _errors['dob'] = 'Date of Birth is required'; - _hasErrors = true; - } else { - final age = DateTime.now().difference(_dateOfBirth!).inDays ~/ 365; - if (age < 18) { - _errors['dob'] = 'User must be at least 18 years old'; - _hasErrors = true; - } - } - - if (_image == null) { - _errors['image'] = 'Profile picture is required'; - _hasErrors = true; - } - - final address = _controller.model.address; - if (address.houseNo?.isEmpty ?? true) { - _errors['address'] = 'Please complete all required address fields'; - _hasErrors = true; - } - - if (address.addressType == 'Other' && - (address.otherLabel?.isEmpty ?? true)) { - _errors['address'] = 'Please specify other address label'; - _hasErrors = true; - } - }); - - return !_hasErrors; - } - - void _showValidationErrors() { - 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'), - ), - ], - ), - ); - } - - Widget _buildNavigationField( - String label, IconData icon, VoidCallback onTap) { - bool isAddressField = label == 'Address'; - bool hasAddressError = _errors.containsKey('address'); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - leading: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: (isAddressField && hasAddressError) - ? Colors.red.withOpacity(0.1) - : Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, - color: (isAddressField && hasAddressError) - ? Colors.red - : Colors.blue, - size: 24), - ), - title: Text( - label, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: (isAddressField && hasAddressError) - ? Colors.red - : Colors.black, - ), - ), - subtitle: isAddressField ? _buildAddressSubtitle() : null, - trailing: Icon( - Icons.chevron_right, - color: - (isAddressField && hasAddressError) ? Colors.red : Colors.blue, - ), - onTap: onTap, - ), - if (isAddressField && hasAddressError) - Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8), - child: Text( - _errors['address']!, - style: const TextStyle( - color: Colors.red, - fontSize: 12, - ), - ), - ), - ], - ); - } - - Widget _buildAddressSubtitle() { - final address = _controller.model.address; - if (address.houseNo == null || - address.line == null || - address.city == null) { - return const Text( - 'No address added', - style: TextStyle(color: Colors.grey), - ); - } - - return Text( - '${address.houseNo}, ${address.line}\n' - '${address.city}, ${address.state} ${address.pincode}\n' - '${address.addressType}${address.addressType == "Other" ? ": ${address.otherLabel}" : ""}', - style: const TextStyle(color: Colors.black87), - ); - } -} diff --git a/lib/telemednet_app.dart b/lib/telemednet_app.dart index 28e81ae..763aae4 100644 --- a/lib/telemednet_app.dart +++ b/lib/telemednet_app.dart @@ -20,9 +20,8 @@ class _TelemednetAppState extends State { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - initialRoute: RouteNames.splashScreen, + initialRoute: RouteNames.signIn, routes: { - RouteNames.splashScreen: (context) => const SplashScreen(), ...routes, }, ); diff --git a/pubspec.lock b/pubspec.lock index dd032bd..0405213 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" country_state_city: dependency: "direct main" description: @@ -423,10 +423,10 @@ packages: dependency: "direct main" description: name: flutter_slidable - sha256: "2c5611c0b44e20d180e4342318e1bbc28b0a44ad2c442f5df16962606fd3e8e3" + sha256: ab7dbb16f783307c9d7762ede2593ce32c220ba2ba0fd540a3db8e9a3acba71a url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.0" flutter_staggered_animations: dependency: "direct main" description: @@ -601,18 +601,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -761,7 +761,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -782,10 +782,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -798,10 +798,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -814,10 +814,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" typed_data: dependency: transitive description: @@ -870,10 +870,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" web: dependency: transitive description: @@ -915,5 +915,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.3 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index dca2948..0ef3cd3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,7 @@ dependencies: country_state_city: ^0.1.6 country_state_city_picker: ^1.2.8 intl_phone_field: ^3.2.0 - flutter_slidable: ^3.1.1 + flutter_slidable: ^4.0.0 gap: ^3.0.1 curved_navigation_bar: ^1.0.6 google_fonts: ^6.2.1