feature/medora-125 (#9)

fixed bugs

Co-authored-by: DhanshCOSQ <dhanshas@cosq.net>
Co-authored-by: Jipson George <152465898+Jipson-cosq@users.noreply.github.com>
Reviewed-on: cosqnet/telemednet#9
Reviewed-by: Benoy Bose <benoybose@cosq.net>
Co-authored-by: Jipson George <jipsongeorge@cosq.net>
Co-committed-by: Jipson George <jipsongeorge@cosq.net>
This commit is contained in:
Jipson George 2024-11-14 11:00:28 +00:00 committed by Benoy Bose
parent 42543367a4
commit 520c9b6e44
22 changed files with 1253 additions and 660 deletions

View File

@ -1,4 +1,4 @@
import '../data/models/consultation_center.dart';
import 'package:medora/data/models/consultation_center.dart';
class ConsultationCenterController {
final ConsultationCenter model;

View File

@ -97,11 +97,11 @@ class DoctorController {
}
void addQualification(String qualification) {
model.qualifications!.add(qualification.trim());
model.qualifications!.add(qualification);
}
void removeQualification(String qualification) {
model.qualifications!.remove(qualification.trim());
model.qualifications!.remove(qualification);
}
void updateFloorBuilding(String floorBuilding) {

View File

@ -2,6 +2,7 @@ class Doctor {
List<String>? achievements;
String? uid;
String? profileImage;
String? profileImageUrl;
String? speciality;
String? yearsOfExperience;
String? licenseNumber;
@ -23,6 +24,7 @@ class Doctor {
Doctor({
this.addressType,
this.achievements,
this.profileImageUrl,
this.profileImage, // Initialize with empty list
this.speciality,
this.yearsOfExperience,
@ -44,6 +46,7 @@ class Doctor {
});
Map<String, dynamic> toJson() => {
'profileImagePath': profileImageUrl,
'profileImage': profileImage,
'achievements': achievements,
'speciality': speciality,
@ -67,6 +70,7 @@ class Doctor {
static Doctor fromJson(Map<String, dynamic> json) => Doctor(
achievements: List<String>.from(json['achievements'] ?? []),
profileImageUrl: json['profileImageUrl'],
profileImage: json['profileImage'],
speciality: json['speciality'],
yearsOfExperience: json['yearsOfExperience'],

View File

@ -2,7 +2,7 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import '../models/consultation_center.dart';
import 'package:medora/data/models/consultation_center.dart';
class ConsultationCenterService {
static final String consultationCenterCollectionName =

View File

@ -1,13 +1,184 @@
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/doctor_controller.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:path/path.dart' as path;
class DoctorProfileService {
static final String doctorProfileCollectionName =
dotenv.env['DOCTOR_PROFILE_COLLECTION_NAME']!;
static final FirebaseFirestore db = FirebaseFirestore.instance;
static final FirebaseStorage storage = FirebaseStorage.instanceFor(
bucket: dotenv.env['FIREBASE_STORAGE_BUCKET']!);
static Future<String?> 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 =
'doctor_profile_${uid}_${DateTime.now().millisecondsSinceEpoch}${path.extension(imageFile.path)}';
final Reference storageRef =
storage.ref().child('doctor_profile_images/$fileName');
final UploadTask uploadTask = storageRef.putFile(
imageFile,
SettableMetadata(
contentType: 'image/${path.extension(imageFile.path).substring(1)}',
customMetadata: {
'userId': uid,
'userType': 'doctor',
'uploadedAt': DateTime.now().toIso8601String(),
},
),
);
final TaskSnapshot snapshot = await uploadTask;
final String downloadUrl = await snapshot.ref.getDownloadURL();
print('Doctor profile image uploaded successfully');
return downloadUrl;
} catch (e) {
print('Error uploading doctor profile image: $e');
return null;
}
}
static Future<bool> deleteProfileImage(String imageUrl) async {
try {
final Reference storageRef = storage.refFromURL(imageUrl);
await storageRef.delete();
print('Doctor profile image deleted successfully');
return true;
} catch (e) {
print('Error deleting doctor profile image: $e');
return false;
}
}
static Future<bool> saveDoctorProfile(DoctorController 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 Doctor doctorData = controller.model;
String? imageUrl;
if (doctorData.profileImage != null) {
final File imageFile = File(doctorData.profileImage!);
imageUrl = await uploadProfileImage(imageFile);
if (imageUrl == null) {
print('Failed to upload doctor profile image');
return false;
}
}
final Map<String, dynamic> doctorJson = doctorData.toJson();
doctorJson['createdAt'] = FieldValue.serverTimestamp();
doctorJson['updatedAt'] = FieldValue.serverTimestamp();
doctorJson['uid'] = uid;
doctorJson['profileImageUrl'] = imageUrl;
await db.collection(doctorProfileCollectionName).doc(uid).set(doctorJson);
print('Doctor profile saved successfully');
return true;
} catch (e) {
print('Error saving doctor profile: $e');
return false;
}
}
static Future<bool> updateDoctorProfile(Doctor doctor) 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 (doctor.profileImage != null) {
// Delete old profile image if exists
final DocumentSnapshot oldDoc =
await db.collection(doctorProfileCollectionName).doc(uid).get();
if (oldDoc.exists) {
final oldData = oldDoc.data() as Map<String, dynamic>;
final String? oldImageUrl = oldData['profileImageUrl'];
if (oldImageUrl != null) {
await deleteProfileImage(oldImageUrl);
}
}
// Upload new profile image
final File imageFile = File(doctor.profileImage!);
imageUrl = await uploadProfileImage(imageFile);
if (imageUrl == null) {
print('Failed to upload new doctor profile image');
return false;
}
}
final Map<String, dynamic> doctorJson = doctor.toJson();
doctorJson['updatedAt'] = FieldValue.serverTimestamp();
if (imageUrl != null) {
doctorJson['profileImageUrl'] = imageUrl;
}
await db
.collection(doctorProfileCollectionName)
.doc(uid)
.update(doctorJson);
print('Doctor profile updated successfully');
return true;
} catch (e) {
print('Error updating doctor profile: $e');
return false;
}
}
static Future<bool> deleteDoctorProfile() async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user == null) {
print('No user logged in');
return false;
}
final String uid = user.uid;
// Delete profile image if exists
final DocumentSnapshot doc =
await db.collection(doctorProfileCollectionName).doc(uid).get();
if (doc.exists) {
final data = doc.data() as Map<String, dynamic>;
final String? imageUrl = data['profileImageUrl'];
if (imageUrl != null) {
await deleteProfileImage(imageUrl);
}
}
await db.collection(doctorProfileCollectionName).doc(uid).delete();
print('Doctor profile deleted successfully');
return true;
} catch (e) {
print('Error deleting doctor profile: $e');
return false;
}
}
static Future<Doctor?> getDoctorProfile() async {
try {
@ -22,7 +193,7 @@ class DoctorProfileService {
await db.collection(doctorProfileCollectionName).doc(uid).get();
if (!doc.exists) {
print('No patient profile found for this user');
print('No doctor profile found for this user');
return null;
}
@ -34,72 +205,16 @@ class DoctorProfileService {
}
}
static Future saveDoctorProfile(DoctorController controller) async {
static Future<bool> isLicenseNumberDuplicate(String licenseNumber) async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user == null) {
print('No user logged in');
return false;
}
final String uid = user.uid;
final Doctor doctorData = controller.model;
final Map<String, dynamic> doctorJson = doctorData.toJson();
doctorJson['createdAt'] = FieldValue.serverTimestamp();
doctorJson['updatedAt'] = FieldValue.serverTimestamp();
doctorJson['uid'] = uid;
await db.collection(doctorProfileCollectionName).doc(uid).set(doctorJson);
print('Doctor profile saved successfully');
return true;
} catch (e) {
print('Error saving doctor profile: $e');
return false;
}
}
static Future updateDoctorProfile(DoctorController 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 Doctor doctorData = controller.model;
final Map<String, dynamic> doctorJson = doctorData.toJson();
doctorJson['updatedAt'] = FieldValue.serverTimestamp();
await db
final querySnapshot = await db
.collection(doctorProfileCollectionName)
.doc(uid)
.update(doctorJson);
print('Doctor profile updated successfully');
return true;
.where('licenseNumber', isEqualTo: licenseNumber)
.get();
return querySnapshot.docs.isNotEmpty;
} catch (e) {
print('Error updating doctor profile: $e');
return false;
}
}
static Future deleteDoctorProfile() async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user == null) {
print('No user logged in');
return false;
}
final String uid = user.uid;
await db.collection(doctorProfileCollectionName).doc(uid).delete();
print('Doctor profile deleted successfully');
return true;
} catch (e) {
print('Error deleting doctor profile: $e');
print('Error checking license number: $e');
return false;
}
}

View File

@ -11,7 +11,7 @@ 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';
import 'package:medora/screens/doctor_screen/doctor_consultation_schedule/consultation_day_screen.dart';
import 'package:medora/screens/doctor_screen/doctor_consultation_schedule/consultation_schedule.dart';
import 'package:medora/screens/doctor_screen/doctor_consultation_schedule/consultation_centers_screen.dart';
import 'package:medora/screens/doctor_screen/doctor_consultation_schedule/consultation_time_slot_screen.dart';
import 'package:medora/screens/doctor_screen/doctor_dashboard/doctor_dashboard_home_screen.dart';
import 'package:medora/screens/doctor_screen/doctor_dashboard/doctor_dashboard_screen.dart';

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:medora/data/services/data_service.dart';
@ -24,6 +26,123 @@ class _SignUpScreenState extends State<SignUpScreen> {
bool _isLoading = false;
bool _obscurePassword = true;
// Add focus nodes to handle keyboard navigation
final _emailFocusNode = FocusNode();
final _passwordFocusNode = FocusNode();
// Email validation patterns
final RegExp _emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
caseSensitive: false,
);
// Common email domain validation
final List<String> _commonEmailDomains = [
'gmail.com',
'yahoo.com',
'hotmail.com',
'outlook.com'
];
String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email address is required';
}
value = value.trim();
// Basic format validation
if (!_emailRegex.hasMatch(value)) {
return 'Please enter a valid email address';
}
// Check email length
if (value.length > 254) {
return 'Email address is too long';
}
// Split email into local and domain parts
final parts = value.split('@');
if (parts.length != 2) {
return 'Invalid email format';
}
final localPart = parts[0];
final domainPart = parts[1].toLowerCase();
// Validate local part
if (localPart.isEmpty || localPart.length > 64) {
return 'Invalid email username';
}
// Check for common typos in popular domains
for (final domain in _commonEmailDomains) {
if (domainPart.length > 3 &&
domainPart != domain &&
_calculateLevenshteinDistance(domainPart, domain) <= 2) {
return 'Did you mean @$domain?';
}
}
// Additional domain validation
if (!domainPart.contains('.')) {
return 'Invalid email domain';
}
return null;
}
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
if (!value.contains(RegExp(r'[A-Z]'))) {
return 'Password must contain at least one uppercase letter';
}
if (!value.contains(RegExp(r'[0-9]'))) {
return 'Password must contain at least one number';
}
if (!value.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]'))) {
return 'Password must contain at least one special character';
}
return null;
}
// Calculate Levenshtein distance for typo detection
int _calculateLevenshteinDistance(String a, String b) {
if (a.isEmpty) return b.length;
if (b.isEmpty) return a.length;
List<int> previousRow = List<int>.generate(b.length + 1, (i) => i);
List<int> currentRow = List<int>.filled(b.length + 1, 0);
for (int i = 0; i < a.length; i++) {
currentRow[0] = i + 1;
for (int j = 0; j < b.length; j++) {
int insertCost = previousRow[j + 1] + 1;
int deleteCost = currentRow[j] + 1;
int replaceCost = previousRow[j] + (a[i] != b[j] ? 1 : 0);
currentRow[j + 1] = [insertCost, deleteCost, replaceCost].reduce(min);
}
List<int> temp = previousRow;
previousRow = currentRow;
currentRow = temp;
}
return previousRow[b.length];
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -51,6 +170,7 @@ class _SignUpScreenState extends State<SignUpScreen> {
children: [
TextFormField(
controller: _emailController,
focusNode: _emailFocusNode,
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(
@ -65,22 +185,25 @@ class _SignUpScreenState extends State<SignUpScreen> {
),
prefixIcon: const Icon(Icons.email_outlined,
color: Colors.blue),
errorMaxLines: 2,
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your email';
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) {
_passwordFocusNode.requestFocus();
},
validator: _validateEmail,
onChanged: (value) {
// Trigger validation on change if the field was previously invalid
if (_formKey.currentState?.validate() ?? false) {
setState(() {});
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
.hasMatch(value!)) {
return 'Please enter a valid email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
focusNode: _passwordFocusNode,
decoration: InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(
@ -106,16 +229,15 @@ class _SignUpScreenState extends State<SignUpScreen> {
onPressed: () => setState(
() => _obscurePassword = !_obscurePassword),
),
errorMaxLines: 2,
),
obscureText: _obscurePassword,
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your password';
validator: _validatePassword,
onChanged: (value) {
// Trigger validation on change if the field was previously invalid
if (_formKey.currentState?.validate() ?? false) {
setState(() {});
}
if ((value?.length ?? 0) < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
const SizedBox(height: 16),
@ -140,7 +262,7 @@ class _SignUpScreenState extends State<SignUpScreen> {
},
validator: (phone) {
if (phone?.completeNumber.isEmpty ?? true) {
return 'Please enter your phone number';
return 'Phone number is required';
}
return null;
},
@ -165,6 +287,7 @@ class _SignUpScreenState extends State<SignUpScreen> {
Future<void> _handleSignUp() async {
if (_formKey.currentState == null || !_formKey.currentState!.validate()) {
_showErrorSnackBar('Please fix the errors in the form');
return;
}
@ -222,6 +345,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_emailFocusNode.dispose();
_passwordFocusNode.dispose();
super.dispose();
}
}

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import '../../../route/route_names.dart';
import 'package:medora/route/route_names.dart';
class BusinessCenterScreen extends StatefulWidget {
final ConsultationCenterController controller;
@ -59,7 +58,7 @@ class _ConsultationCenterScreenState extends State<BusinessCenterScreen> {
TextEditingController(text: center.postalCode ?? '');
_addressTypeController =
TextEditingController(text: center.addressType ?? '');
selectedAddressType = widget.controller?.model.addressType;
selectedAddressType = widget.controller.model.addressType;
if (selectedAddressType != null &&
!addressTypes.contains(selectedAddressType)) {
showCustomTypeField = true;
@ -81,32 +80,6 @@ class _ConsultationCenterScreenState extends State<BusinessCenterScreen> {
return false;
}
bool _areFieldsValid() {
return _floorBuildingController.text.isNotEmpty &&
_streetController.text.isNotEmpty &&
_cityController.text.isNotEmpty &&
_stateController.text.isNotEmpty &&
_countryController.text.isNotEmpty &&
_postalCodeController.text.isNotEmpty &&
_addressTypeController.text.isNotEmpty;
}
void _handleAddressTypeSelection(String type) {
setState(() {
if (type == 'Others') {
showCustomTypeField = !showCustomTypeField;
if (!showCustomTypeField) {
_addressTypeController.clear();
selectedAddressType = null;
}
} else {
showCustomTypeField = false;
selectedAddressType = type;
widget.controller?.updateAddressType(type);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -151,35 +124,35 @@ class _ConsultationCenterScreenState extends State<BusinessCenterScreen> {
label: 'Floor, Building',
controller: _floorBuildingController,
onChanged: (value) =>
widget.controller!.updateFloorBuilding(value),
widget.controller.updateFloorBuilding(value),
icon: Icons.apartment,
isMandatory: true,
),
_buildTextField(
label: 'Street or Road',
controller: _streetController,
onChanged: (value) => widget.controller!.updateStreet(value),
onChanged: (value) => widget.controller.updateStreet(value),
icon: Icons.streetview,
isMandatory: true,
),
_buildTextField(
label: 'City',
controller: _cityController,
onChanged: (value) => widget.controller!.updateCity(value),
onChanged: (value) => widget.controller.updateCity(value),
icon: Icons.location_city,
isMandatory: true,
),
_buildTextField(
label: 'State',
controller: _stateController,
onChanged: (value) => widget.controller!.updateState(value),
onChanged: (value) => widget.controller.updateState(value),
icon: Icons.map,
isMandatory: true,
),
_buildTextField(
label: 'Country',
controller: _countryController,
onChanged: (value) => widget.controller!.updateCountry(value),
onChanged: (value) => widget.controller.updateCountry(value),
icon: Icons.flag,
isMandatory: true,
),
@ -187,7 +160,7 @@ class _ConsultationCenterScreenState extends State<BusinessCenterScreen> {
label: 'Postal Code',
controller: _postalCodeController,
onChanged: (value) =>
widget.controller!.updatePostalCode(value),
widget.controller.updatePostalCode(value),
icon: Icons.mail,
isMandatory: true,
),

View File

@ -3,8 +3,8 @@ import 'package:flutter/services.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import 'package:medora/data/services/consultation_center_service.dart';
import 'package:medora/route/route_names.dart';
import 'package:medora/screens/doctor_screen/doctor_consultation_schedule/consultation_day_screen.dart';
import '../../../route/route_names.dart';
class CenterFeeAndDurationScreen extends StatefulWidget {
final ConsultationCenterController controller;
@ -25,7 +25,6 @@ class CenterFeeAndDurationScreenState
late final ConsultationCenterController _controller;
late TextEditingController _averageDuration;
late TextEditingController _consultationFee;
bool _isEditing = false;
@override
void initState() {
@ -46,9 +45,7 @@ class CenterFeeAndDurationScreenState
}
void _onFieldChanged() {
setState(() {
_isEditing = true;
});
setState(() {});
}
void _showError(String message) {
@ -85,12 +82,14 @@ class CenterFeeAndDurationScreenState
await ConsultationCenterService.saveConsultationCenters(controllers);
if (success) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Doctor consultation saved successfully!'),
backgroundColor: Colors.green,
),
);
// ignore: use_build_context_synchronously
Navigator.of(context)
.pushReplacementNamed(RouteNames.scheduleConsultationScreen);
} else {
@ -146,6 +145,7 @@ class CenterFeeAndDurationScreenState
TextFormField(
controller: _consultationFee,
keyboardType: TextInputType.number,
textAlign: TextAlign.end,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
validator: (value) {
if (value == null || value.isEmpty) {
@ -191,6 +191,7 @@ class CenterFeeAndDurationScreenState
const SizedBox(height: 24),
TextFormField(
controller: _averageDuration,
textAlign: TextAlign.end,
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {

View File

@ -1,11 +1,10 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:medora/common/custom_style.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/services/consultation_center_service.dart';
import '../../../common/custom_style.dart';
import '../../../route/route_names.dart';
import 'package:medora/route/route_names.dart';
class ScheduleConsultationScreen extends StatefulWidget {
const ScheduleConsultationScreen({super.key});
@ -64,7 +63,7 @@ class ScheduleConsultationScreenState
context, RouteNames.doctorDashbordScreen),
),
title: Text(
'Schedule Consultation',
'Consultation Centers',
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
),
@ -114,7 +113,7 @@ class ScheduleConsultationScreenState
color: Theme.of(context).colorScheme.onSurface,
),
),
trailing: Container(
trailing: SizedBox(
width: 80, // Increased width to accommodate contents
child: Row(
mainAxisSize:
@ -270,10 +269,17 @@ class ScheduleConsultationScreenState
size: 18,
),
onPressed: () {
// Pass the existing center data to maintain state
// final consultationController =
// ConsultationCenterController(
// center);
// Navigator.pushNamed(
// context,
// RouteNames
// .ConsultationDayScreen,
// .consultationDayScreen,
// arguments:
// consultationController,
// );
},
),

View File

@ -52,6 +52,15 @@ class _ConsultationTimeSlotScreenState
'Initial schedule for ${widget.selectedDay}: ${currentSchedule.timeSlots}');
}
String formatTime(TimeOfDay time) {
final hour = time.hourOfPeriod == 0
? 12
: time.hourOfPeriod; // Convert 0 to 12 for AM
final minute = time.minute.toString().padLeft(2, '0');
final period = time.period == DayPeriod.am ? 'AM' : 'PM';
return '$hour:$minute $period';
}
void _addTimeSlot() async {
TimeOfDay? startTime = await showTimePicker(
context: context,
@ -66,74 +75,57 @@ class _ConsultationTimeSlotScreenState
if (endTime != null) {
final slot = TimeSlot(
startTime:
'${startTime.hour}:${startTime.minute.toString().padLeft(2, '0')}',
endTime:
'${endTime.hour}:${endTime.minute.toString().padLeft(2, '0')}',
startTime: formatTime(startTime),
endTime: formatTime(endTime),
);
if (mounted) {
setState(() {
widget.controller.addTimeSlot(widget.selectedDay, slot);
// Update local schedule
currentSchedule =
widget.controller.model.weeklySchedule?.firstWhere(
(schedule) => schedule.day == widget.selectedDay,
orElse: () => AvailabilitySchedule(
day: widget.selectedDay,
timeSlots: [],
),
) ??
AvailabilitySchedule(
day: widget.selectedDay,
timeSlots: [],
);
widget.controller.model.weeklySchedule!.firstWhere(
(schedule) => schedule.day == widget.selectedDay,
orElse: () => AvailabilitySchedule(
day: widget.selectedDay,
timeSlots: [],
),
);
});
// Debug print
print('Added time slot: $slot');
print('Updated schedule: ${currentSchedule.timeSlots}');
}
}
}
}
void _editTimeSlot(TimeSlot currentSlot) async {
final currentStart = currentSlot.startTime!.split(':');
final currentEnd = currentSlot.endTime!.split(':');
final currentStart = currentSlot.startTime!.split(' ');
currentSlot.endTime!.split(' ');
TimeOfDay? startTime = await showTimePicker(
context: context,
initialTime: TimeOfDay(
hour: int.parse(currentStart[0]),
minute: int.parse(currentStart[1]),
),
final startTime = TimeOfDay(
hour: int.parse(currentStart[0].split(':')[0]),
minute: int.parse(currentStart[0].split(':')[1]),
);
if (startTime != null) {
TimeOfDay? endTime = await showTimePicker(
TimeOfDay? newStartTime = await showTimePicker(
context: context,
initialTime: startTime,
);
if (newStartTime != null) {
TimeOfDay? newEndTime = await showTimePicker(
context: context,
initialTime: TimeOfDay(
hour: int.parse(currentEnd[0]),
minute: int.parse(currentEnd[1]),
),
initialTime: newStartTime,
);
if (endTime != null && mounted) {
if (newEndTime != null && mounted) {
setState(() {
// Remove old slot
widget.controller.removeTimeSlot(widget.selectedDay, currentSlot);
// Add new slot
final newSlot = TimeSlot(
startTime:
'${startTime.hour}:${startTime.minute.toString().padLeft(2, '0')}',
endTime:
'${endTime.hour}:${endTime.minute.toString().padLeft(2, '0')}',
startTime: formatTime(newStartTime),
endTime: formatTime(newEndTime),
);
widget.controller.addTimeSlot(widget.selectedDay, newSlot);
// Update local schedule
currentSchedule = widget.controller.model.weeklySchedule!.firstWhere(
(schedule) => schedule.day == widget.selectedDay,
orElse: () => AvailabilitySchedule(
@ -142,9 +134,6 @@ class _ConsultationTimeSlotScreenState
),
);
});
// Debug print
// print('Edited time slot from $currentSlot to $newSlot');
}
}
}
@ -314,7 +303,7 @@ class _ConsultationTimeSlotScreenState
title: Text(
'${slot.startTime} - ${slot.endTime}',
style: const TextStyle(
fontSize: 20,
fontSize: 13.5,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
@ -325,7 +314,7 @@ class _ConsultationTimeSlotScreenState
IconButton(
icon: const Icon(
Icons.edit_outlined,
size: 30,
size: 26,
color: Colors.blue,
),
onPressed: () => _editTimeSlot(slot),
@ -333,7 +322,7 @@ class _ConsultationTimeSlotScreenState
IconButton(
icon: const Icon(
Icons.delete_outline,
size: 30,
size: 26,
color: Colors.red,
),
onPressed: () => _deleteTimeSlot(slot),

View File

@ -1,7 +1,10 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:medora/screens/patient_screens/appoinment_bookings/speciality_screen.dart';
import 'package:medora/data/models/consultation_booking.dart';
import 'package:medora/data/services/doctor_profile_service.dart';
class DoctorDashboardHomeScreen extends StatefulWidget {
const DoctorDashboardHomeScreen({super.key});
@ -14,6 +17,7 @@ class DoctorDashboardHomeScreen extends StatefulWidget {
class _DoctorDashboardHomeScreenState extends State<DoctorDashboardHomeScreen>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
Doctor? _doctorProfile;
@override
void initState() {
@ -23,12 +27,23 @@ class _DoctorDashboardHomeScreenState extends State<DoctorDashboardHomeScreen>
duration: const Duration(milliseconds: 300),
);
_animationController.forward();
_fetchDoctorProfile();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
_fetchDoctorProfile();
}
Future<void> _fetchDoctorProfile() async {
final doctorProfile = await DoctorProfileService.getDoctorProfile();
if (mounted) {
setState(() {
_doctorProfile = doctorProfile;
});
}
}
@override
@ -41,10 +56,17 @@ class _DoctorDashboardHomeScreenState extends State<DoctorDashboardHomeScreen>
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: [_buildRealTimeCard(), const SizedBox(height: 20)],
children: [
_buildRealTimeCard(),
const SizedBox(height: 20),
_buildUpcomingBookings(),
const SizedBox(
height: 20,
),
// _buildCompletedConsultations()
],
),
),
// _buildBottomNavBar(),
],
),
),
@ -112,7 +134,7 @@ class _DoctorDashboardHomeScreenState extends State<DoctorDashboardHomeScreen>
Widget _buildRealTimeCard() {
return Container(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(15), // Reduced padding
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue[400]!, Colors.white],
@ -132,36 +154,286 @@ class _DoctorDashboardHomeScreenState extends State<DoctorDashboardHomeScreen>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Real-time care\nat your fingertips.',
'Instant patient insights\nright at your fingertips',
style: GoogleFonts.poppins(
fontSize: 30,
fontSize: 24, // Reduced font size
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),
const SizedBox(height: 8), // Reduced spacing
// 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: 12, vertical: 5), // Reduced padding
// // shape: RoundedRectangleBorder(
// // borderRadius: BorderRadius.circular(30),
// // ),
// // elevation: 5,
// // ),
// // child: Text(
// // 'Start Consultation',
// // style: GoogleFonts.poppins(
// // fontWeight: FontWeight.bold,
// // fontSize: 14, // Reduced font size
// // ),
// // ),
// ),
],
),
);
}
Widget _buildUpcomingBookings() {
return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('bookings')
.where('doctorId', isEqualTo: _doctorProfile?.uid!)
.orderBy('appointmentDate', descending: true)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text('Error loading bookings'));
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final bookings = snapshot.data!.docs
.map((doc) => Booking.fromMap(doc.data()))
.toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Upcoming Bookings',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
elevation: 5,
),
child: Text(
'Start Consultation',
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
const SizedBox(height: 12),
if (bookings.isNotEmpty)
SizedBox(
height: 220, // Adjust height as needed
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: bookings.length,
itemBuilder: (context, index) {
final booking = bookings[index];
return Padding(
padding: const EdgeInsets.only(right: 16),
child: _consultationCard(
booking.patientName,
'${DateFormat('EEE, MMM d, yyyy').format(booking.appointmentDate)}\n${booking.appointmentTime}',
booking.paymentStatus,
),
);
},
),
),
if (bookings.isEmpty)
Center(
child: Text(
'No upcoming bookings',
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.grey[600],
),
),
),
],
);
},
);
}
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 _consultationCard(
String name,
String schedule,
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: [
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],
),
),
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,
),
),
],
),
),
],

View File

@ -1,5 +1,7 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:medora/data/services/doctor_profile_service.dart';
import 'package:medora/route/route_names.dart';
class DoctorPersonalProfileScreen extends StatefulWidget {
@ -13,6 +15,23 @@ class DoctorPersonalProfileScreen extends StatefulWidget {
class _DoctorPersonalProfileScreen extends State<DoctorPersonalProfileScreen> {
final FirebaseAuth _auth = FirebaseAuth.instance;
// final GlobalKey<CurvedNavigationBarState> _bottomNavigationKey = GlobalKey();
Doctor? _doctorProfile;
@override
void initState() {
super.initState();
_fetchDoctorProfile();
}
Future<void> _fetchDoctorProfile() async {
final doctorProfile = await DoctorProfileService.getDoctorProfile();
if (mounted) {
setState(() {
_doctorProfile = doctorProfile;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -51,35 +70,45 @@ class _DoctorPersonalProfileScreen extends State<DoctorPersonalProfileScreen> {
Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
image: _doctorProfile?.profileImageUrl != null
? DecorationImage(
image: NetworkImage(_doctorProfile!.profileImageUrl!),
fit: BoxFit.cover,
)
: null,
),
child: const Center(
child: Text(
'D',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.blue,
// child: Text(
// _doctorProfile != null && _doctorProfile!.firstName != null
// ? _doctorProfile!.firstName![0].toUpperCase()
// : '',
// style: const TextStyle(
// fontSize: 30,
// fontWeight: FontWeight.bold,
// color: Colors.blue,
// ),
// ),
),
),
),
),
const SizedBox(width: 16),
const Expanded(
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Doctor',
style: TextStyle(
_doctorProfile != null && _doctorProfile!.firstName != null
? _doctorProfile!.firstName!
: 'Welcome',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
const Text(
'Personal Profile',
style: TextStyle(
fontSize: 14,

View File

@ -1,5 +1,6 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:medora/data/services/doctor_profile_service.dart';
import 'package:medora/route/route_names.dart';
class DoctorServicesMenuScreen extends StatefulWidget {
@ -10,8 +11,23 @@ class DoctorServicesMenuScreen extends StatefulWidget {
}
class _DoctorServicesMenuScreen extends State<DoctorServicesMenuScreen> {
final FirebaseAuth _auth = FirebaseAuth.instance;
Doctor? _doctorProfile;
// final GlobalKey<CurvedNavigationBarState> _bottomNavigationKey = GlobalKey();
@override
void initState() {
super.initState();
_fetchDoctorProfile();
}
Future<void> _fetchDoctorProfile() async {
final doctorProfile = await DoctorProfileService.getDoctorProfile();
if (mounted) {
setState(() {
_doctorProfile = doctorProfile;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -50,35 +66,48 @@ class _DoctorServicesMenuScreen extends State<DoctorServicesMenuScreen> {
Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
image: _doctorProfile?.profileImageUrl != null
? DecorationImage(
image: NetworkImage(_doctorProfile!.profileImageUrl!),
fit: BoxFit.cover,
)
: null,
),
child: const Center(
child: Text(
'D',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
child: _doctorProfile?.profileImageUrl == null
? Center(
child: Text(
_doctorProfile != null &&
_doctorProfile!.firstName != null
? _doctorProfile!.firstName![0].toUpperCase()
: '',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
)
: null,
),
const SizedBox(width: 16),
const Expanded(
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Doctor',
style: TextStyle(
_doctorProfile != null && _doctorProfile!.firstName != null
? _doctorProfile!.firstName!
: 'Welcome',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
const Text(
'See our services below',
style: TextStyle(
fontSize: 14,
@ -88,11 +117,6 @@ class _DoctorServicesMenuScreen extends State<DoctorServicesMenuScreen> {
],
),
),
const Icon(
Icons.chevron_right,
color: Colors.white,
size: 30,
),
],
),
);
@ -115,7 +139,7 @@ class _DoctorServicesMenuScreen extends State<DoctorServicesMenuScreen> {
child: Column(
children: [
_buildOptionTile(
'Schedule consultation',
'Consultation Centers',
Icons.medical_information_outlined,
onTap: () {
Navigator.of(context)
@ -159,21 +183,4 @@ class _DoctorServicesMenuScreen extends State<DoctorServicesMenuScreen> {
onTap: onTap,
);
}
Future<void> _signOut() async {
try {
await _auth.signOut();
if (mounted) {
Navigator.of(context)
.pushReplacementNamed(RouteNames.scheduleConsultationScreen);
}
} catch (e) {
print("Error signing out: $e");
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to log out. Please try again.')),
);
}
}
}
}

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
import 'package:medora/route/route_names.dart';
class AchievementsScreen extends StatefulWidget {
final DoctorController controller;
@ -57,10 +56,6 @@ class _AchievementsScreenState extends State<AchievementsScreen> {
_showError('Achievement must be at least 3 characters long');
return false;
}
if (!RegExp(r'^[a-zA-Z0-9\s.,]+$').hasMatch(value)) {
_showError('Please enter valid achievement text');
return false;
}
return true;
}

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
import 'package:medora/route/route_names.dart';
class DoctorAddressScreen extends StatefulWidget {
final DoctorController? controller;
@ -32,121 +31,81 @@ class _DoctorAddressScreenState extends State<DoctorAddressScreen> {
void initState() {
super.initState();
_controller = widget.controller ?? DoctorController();
_floorBuildingController = TextEditingController();
_streetController = TextEditingController();
_cityController = TextEditingController();
_stateController = TextEditingController();
_countryController = TextEditingController();
_postalCodeController = TextEditingController();
_addressTypeController = TextEditingController();
_loadSavedData();
}
@override
void dispose() {
_floorBuildingController.dispose();
_streetController.dispose();
_cityController.dispose();
_stateController.dispose();
_countryController.dispose();
_postalCodeController.dispose();
_addressTypeController.dispose();
super.dispose();
}
void _loadSavedData() {
final doctor = _controller.model;
_floorBuildingController =
TextEditingController(text: doctor.floorBuilding ?? '');
_streetController = TextEditingController(text: doctor.street ?? '');
_cityController = TextEditingController(text: doctor.city ?? '');
_stateController = TextEditingController(text: doctor.state ?? '');
_countryController = TextEditingController(text: doctor.country ?? '');
_postalCodeController =
TextEditingController(text: doctor.postalCode ?? '');
_addressTypeController =
TextEditingController(text: doctor.addressType ?? '');
selectedAddressType = widget.controller?.model.addressType;
if (selectedAddressType != null &&
!addressTypes.contains(selectedAddressType)) {
showCustomTypeField = true;
_floorBuildingController.text = doctor.floorBuilding ?? '';
_streetController.text = doctor.street ?? '';
_cityController.text = doctor.city ?? '';
_stateController.text = doctor.state ?? '';
_countryController.text = doctor.country ?? '';
_postalCodeController.text = doctor.postalCode ?? '';
// Proper handling of address type
if (doctor.addressType != null) {
final savedType = doctor.addressType!;
if (addressTypes.contains(savedType)) {
selectedAddressType = savedType;
showCustomTypeField = false;
} else {
selectedAddressType = 'Others';
showCustomTypeField = true;
_addressTypeController.text = savedType;
}
}
}
// bool _validateAndProceed() {if (_formKey.currentState!.validate()) {
// // Update the address model
// _controller.updateFloorBuilding(_floorBuildingController.text);
// _controller.updateStreet(_streetController.text);
// _controller.updateCity(_cityController.text);
// _controller.updateState(_stateController.text);
// _controller.updateCountry(_countryController.text);
// _controller.updatePostalCode(_postalCodeController.text);
void _handleAddressTypeSelection(String type) {
setState(() {
if (type == 'Others') {
showCustomTypeField = true;
selectedAddressType = type;
// Don't clear the custom field if it was previously set
if (_addressTypeController.text.isEmpty) {
_controller.updateAddressType(''); // Clear the controller value
}
} else {
showCustomTypeField = false;
selectedAddressType = type;
_addressTypeController.clear();
_controller.updateAddressType(
type); // Update controller with the selected type
}
});
}
// // Validate the address fields
// if (_areFieldsValid()) {
// return true;
// }
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(content: Text('Please fill in all required fields')),
// );
// return false;
// }
bool _validateAndProceed() {
// if (!_formKey.currentState!.validate()) return false;
// if (selectedAddressType == null) {
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text('Please select an address type'),
// backgroundColor: Colors.red,
// ));
// }
if (_formKey.currentState!.validate()) {
// Save all form data
_controller.updateFloorBuilding(_floorBuildingController.text);
_controller.updateStreet(_streetController.text);
_controller.updateCity(_cityController.text);
_controller.updateState(_stateController.text);
_controller.updateCountry(_countryController.text);
_controller.updatePostalCode(_postalCodeController.text);
_controller.updateAddressType(_addressTypeController.text);
// Handle address type saving
if (showCustomTypeField && _addressTypeController.text.isNotEmpty) {
_controller.updateAddressType(_addressTypeController.text);
} else if (selectedAddressType != null &&
selectedAddressType != 'Others') {
_controller.updateAddressType(selectedAddressType!);
}
return true;
}
return false;
}
bool _areFieldsValid() {
return _floorBuildingController.text.isNotEmpty &&
_streetController.text.isNotEmpty &&
_cityController.text.isNotEmpty &&
_stateController.text.isNotEmpty &&
_countryController.text.isNotEmpty &&
_postalCodeController.text.isNotEmpty &&
_addressTypeController.text.isNotEmpty;
}
// bool _validateAndProceed() {
// if (!_formKey.currentState!.validate()) return false;
// if (selectedAddressType == null) {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: Text('Please select an address type'),
// backgroundColor: Colors.red,
// ),
// );
// return false;
// }
// return true;
// }
void _handleAddressTypeSelection(String type) {
setState(() {
if (type == 'Others') {
showCustomTypeField = !showCustomTypeField;
if (!showCustomTypeField) {
_addressTypeController.clear();
selectedAddressType = null;
}
} else {
showCustomTypeField = false;
selectedAddressType = type;
widget.controller?.updateAddressType(type);
}
});
}
Widget _buildAddressTypeChips() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -162,16 +121,13 @@ class _DoctorAddressScreenState extends State<DoctorAddressScreen> {
const SizedBox(height: 8),
LayoutBuilder(
builder: (context, constraints) {
// Use the width of the layout to manage chip sizes
final chipMaxWidth = constraints.maxWidth;
return Wrap(
spacing: 6, // Adjusted spacing to help fit in a single row
spacing: 6,
runSpacing: 6,
children: addressTypes.map((type) {
final isSelected =
!showCustomTypeField && selectedAddressType == type ||
(type == 'Others' && showCustomTypeField);
final isSelected = selectedAddressType == type;
IconData icon;
switch (type) {
@ -198,10 +154,10 @@ class _DoctorAddressScreenState extends State<DoctorAddressScreen> {
maxWidth:
(chipMaxWidth - (addressTypes.length - 1) * 6) /
addressTypes.length,
), // Calculate width to fit all chips within row
),
padding: const EdgeInsets.symmetric(
horizontal: 12, // Reduced horizontal padding
vertical: 8, // Reduced vertical padding
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: isSelected
@ -215,14 +171,12 @@ class _DoctorAddressScreenState extends State<DoctorAddressScreen> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon,
color: Colors.blue, size: 16), // Smaller icon
const SizedBox(
width: 4), // Spacing between icon and text
Icon(icon, color: Colors.blue, size: 16),
const SizedBox(width: 4),
Text(
type,
style: TextStyle(
fontSize: 14, // Reduced font size
fontSize: 14,
color: isSelected ? Colors.blue : Colors.black87,
fontWeight: FontWeight.w500,
),
@ -239,33 +193,34 @@ class _DoctorAddressScreenState extends State<DoctorAddressScreen> {
if (showCustomTypeField) ...[
const SizedBox(height: 16),
TextFormField(
controller: _addressTypeController,
decoration: InputDecoration(
labelText: 'Custom Address Type',
prefixIcon:
const Icon(Icons.edit_location_alt, color: Colors.blue),
// filled: true,
// fillColor: Colors.grey[100],
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade300, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1.5),
),
controller: _addressTypeController,
decoration: InputDecoration(
labelText: 'Custom Address Type',
prefixIcon:
const Icon(Icons.edit_location_alt, color: Colors.blue),
contentPadding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade300, width: 1),
),
validator: (value) {
if (showCustomTypeField && (value == null || value.isEmpty)) {
return 'Please enter address type';
}
return null;
},
onChanged: (value) {
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1.5),
),
),
validator: (value) {
if (showCustomTypeField && (value == null || value.isEmpty)) {
return 'Please enter address type';
}
return null;
},
onChanged: (value) {
if (value.isNotEmpty) {
_controller.updateAddressType(value);
}),
}
},
),
],
],
);
@ -434,7 +389,7 @@ class _DoctorAddressScreenState extends State<DoctorAddressScreen> {
if (isMandatory && (value == null || value.isEmpty)) {
return '$label is required';
}
if (value != null && !RegExp(r'^[0-9]+$').hasMatch(value)) {
if (value != null && !RegExp(r'^(?!0{6})\d{6}$').hasMatch(value)) {
return 'Please enter numbers only';
}
return null;

View File

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../data/services/doctor_profile_service.dart';
import '../../../route/route_names.dart';
import 'package:medora/data/services/doctor_profile_service.dart';
import 'package:medora/route/route_names.dart';
class DigitalSignatureScreen extends StatefulWidget {
final DoctorController controller;
@ -59,12 +59,14 @@ class _DigitalSignatureScreenState extends State<DigitalSignatureScreen> {
await DoctorProfileService.saveDoctorProfile(widget.controller);
if (success) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Doctor profile saved successfully!'),
backgroundColor: Colors.green,
),
);
// ignore: use_build_context_synchronously
Navigator.of(context).pushNamed(RouteNames.doctorLandingScreen);
} else {
_showError('Failed to save profile. Please try again.');

View File

@ -23,7 +23,6 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
String? _selectedTitle;
final List<String> _titles = ['Mr', 'Mrs', 'Miss'];
final Map<String, String> _errors = {};
@override
void initState() {
@ -44,8 +43,6 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
super.dispose();
}
bool _isEditing = false;
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -74,16 +71,10 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
Future<void> _getImage(ImageSource source) async {
try {
final XFile? image = await _picker.pickImage(
source: source,
imageQuality: 80,
maxWidth: 1000,
maxHeight: 1000,
);
final XFile? image = await _picker.pickImage(source: source);
if (image != null) {
setState(() {
_image = File(image.path);
_isEditing = true;
});
_controller.updateProfileImage(image.path);
}
@ -156,15 +147,14 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
);
}
void _updateCombinedName(String name) {
String fullName = '$_selectedTitle$name';
_controller.updateSurName(fullName);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
actions: [
IconButton(
onPressed: () {
@ -242,7 +232,6 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
children: [
_buildUniformField(
label: 'Name',
icon: Icons.person,
child:
Container(), // The child parameter is not used in this implementation

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
import 'package:medora/data/services/doctor_profile_service.dart';
import 'package:medora/route/route_names.dart';
class ExperienceScreen extends StatefulWidget {
final DoctorController controller;
@ -21,7 +21,6 @@ class _ExperienceScreenState extends State<ExperienceScreen> {
late final DoctorController _controller;
late TextEditingController _selectedExperience;
late TextEditingController _licenseController;
bool _isEditing = false;
@override
void initState() {
@ -44,9 +43,7 @@ class _ExperienceScreenState extends State<ExperienceScreen> {
}
void _onFieldChanged() {
setState(() {
_isEditing = true;
});
setState(() {});
}
void _showError(String message) {
@ -59,8 +56,17 @@ class _ExperienceScreenState extends State<ExperienceScreen> {
);
}
bool _validateAndProceed() {
Future<bool> _validateAndProceed() async {
if (_formKey.currentState!.validate()) {
// Check for duplicate license number
bool isDuplicate = await DoctorProfileService.isLicenseNumberDuplicate(
_licenseController.text.trim());
if (isDuplicate) {
_showError('This license number is already registered');
return false;
}
_controller.updateYearsOfExperience(_selectedExperience.text);
_controller.updateLicenseNumber(_licenseController.text.trim());
return true;
@ -74,8 +80,8 @@ class _ExperienceScreenState extends State<ExperienceScreen> {
appBar: AppBar(
actions: [
IconButton(
onPressed: () {
if (_validateAndProceed()) {
onPressed: () async {
if (await _validateAndProceed()) {
Navigator.pushNamed(
context,
RouteNames.specialitiesScreeen,

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
import 'package:medora/route/route_names.dart';
class ProfileDescriptionScreen extends StatefulWidget {
final DoctorController? controller;
@ -19,7 +19,6 @@ class _ProfileDescriptionScreenState extends State<ProfileDescriptionScreen> {
final _formKey = GlobalKey<FormState>();
late DoctorController? _controller;
late TextEditingController _descriptionController;
bool _isEditing = false;
final int _minDescriptionLength = 5;
final int _maxDescriptionLength = 500;
@ -40,9 +39,7 @@ class _ProfileDescriptionScreenState extends State<ProfileDescriptionScreen> {
}
void _onDescriptionChanged() {
setState(() {
_isEditing = true;
});
setState(() {});
}
bool _validateDescription() {

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
import 'package:medora/route/route_names.dart';
class QualificationsScreen extends StatefulWidget {
final DoctorController? controller;
@ -18,11 +18,9 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
late DoctorController _controller;
late TextEditingController _qualificationsController;
late List<String> qualifications;
bool _isEditing = false;
bool _showOthersField = false;
final _formKey = GlobalKey<FormState>();
// Predefined popular qualifications
final List<String> popularQualifications = [
'MBBS',
'MD',
@ -42,6 +40,7 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
_controller = widget.controller ?? DoctorController();
_controller.model.qualifications ??= [];
_qualificationsController = TextEditingController();
// Create a deep copy of the qualifications list
qualifications = List<String>.from(_controller.model.qualifications ?? []);
}
@ -50,6 +49,8 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
_showError('Please add at least one qualification');
return false;
}
// Sync the qualifications with the controller before navigation
_controller.model.qualifications = List<String>.from(qualifications);
return true;
}
@ -82,26 +83,10 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
if (_formKey.currentState!.validate() &&
_validateQualification(qualification)) {
// Check if qualification already exists (case-insensitive)
bool isDuplicate = qualifications
.any((q) => q.toLowerCase() == qualification.toLowerCase());
if (!isDuplicate) {
setState(() {
qualifications.add(qualification);
_isEditing = true;
_qualificationsController.clear();
});
_controller.addQualification(qualification);
} else {
// Show error message for duplicate qualification
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('This qualification has already been added'),
backgroundColor: Colors.red,
),
);
}
setState(() {
qualifications.add(qualification);
_qualificationsController.clear();
});
}
}
@ -114,23 +99,14 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
if (qualifications.contains(qualification)) {
qualifications.remove(qualification);
_controller.removeQualification(qualification);
} else {
// Check if qualification already exists (case-insensitive)
bool isDuplicate = qualifications
.any((q) => q.toLowerCase() == qualification.toLowerCase());
if (!isDuplicate) {
qualifications.add(qualification);
_controller.addQualification(qualification);
} else {
// Show error message for duplicate qualification
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('This qualification has already been added'),
backgroundColor: Colors.red,
),
);
_showError('This qualification has already been added');
}
}
});
@ -138,10 +114,9 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
void _removeQualification(int index) {
setState(() {
// Remove the qualification at the specified index
qualifications.removeAt(index);
_isEditing = true;
});
_controller.removeQualification(_qualificationsController.text);
}
void _showError(String message) {
@ -156,175 +131,183 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () {
if (_validateBeforeNextPage()) {
Navigator.pushNamed(
context,
RouteNames.doctorAddressScreen,
arguments: _controller,
);
}
},
icon: const Icon(Icons.arrow_forward),
),
],
title: const Text('Qualifications'),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 5,
blurRadius: 10,
offset: const Offset(0, 3),
),
],
// ignore: deprecated_member_use
return WillPopScope(
onWillPop: () async {
// Sync qualifications with controller before popping
_controller.model.qualifications = List<String>.from(qualifications);
return true;
},
child: Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () {
if (_validateBeforeNextPage()) {
Navigator.pushNamed(
context,
RouteNames.doctorAddressScreen,
arguments: _controller,
);
}
},
icon: const Icon(Icons.arrow_forward),
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(20),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 2.5,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: popularQualifications.length,
itemBuilder: (context, index) {
final qualification = popularQualifications[index];
final isSelected = qualification != 'Others' &&
qualifications.contains(qualification);
final isOthers = qualification == 'Others';
],
title: const Text('Qualifications'),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 5,
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(20),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 2.5,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: popularQualifications.length,
itemBuilder: (context, index) {
final qualification = popularQualifications[index];
final isSelected = qualification != 'Others' &&
qualifications.contains(qualification);
final isOthers = qualification == 'Others';
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _toggleQualification(qualification),
borderRadius: BorderRadius.circular(25),
child: Container(
decoration: BoxDecoration(
color:
isSelected || (isOthers && _showOthersField)
? Colors.blue.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
border: Border.all(
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _toggleQualification(qualification),
borderRadius: BorderRadius.circular(25),
child: Container(
decoration: BoxDecoration(
color: isSelected ||
(isOthers && _showOthersField)
? Colors.blue
: Colors.transparent,
? Colors.blue.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
border: Border.all(
color: isSelected ||
(isOthers && _showOthersField)
? Colors.blue
: Colors.transparent,
),
),
alignment: Alignment.center,
child: Text(
qualification,
style: TextStyle(
color: isSelected ||
(isOthers && _showOthersField)
? Colors.blue
: Colors.black87,
fontWeight: FontWeight.w500,
),
),
),
alignment: Alignment.center,
child: Text(
qualification,
style: TextStyle(
color: isSelected ||
(isOthers && _showOthersField)
? Colors.blue
: Colors.black87,
),
);
},
),
),
if (_showOthersField) ...[
Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue.shade100),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: TextFormField(
controller: _qualificationsController,
decoration: InputDecoration(
hintText: 'Enter your qualification',
border: InputBorder.none,
suffixIcon: IconButton(
icon: const Icon(Icons.add_circle_outline,
color: Colors.blue),
onPressed: _newQualification,
),
),
onFieldSubmitted: (_) => _newQualification(),
),
),
),
const SizedBox(height: 20),
],
if (qualifications.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
'Selected Qualifications',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
),
),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: qualifications.length,
padding: const EdgeInsets.symmetric(horizontal: 20),
itemBuilder: (context, index) {
return Card(
elevation: 0,
color: Colors.blue.withOpacity(0.1),
margin: const EdgeInsets.only(bottom: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
title: Text(
qualifications[index],
style: const TextStyle(
fontWeight: FontWeight.w500,
),
),
trailing: IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
size: 20,
),
onPressed: () => _removeQualification(index),
),
),
),
);
},
),
),
if (_showOthersField) ...[
Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue.shade100),
);
},
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: TextFormField(
controller: _qualificationsController,
decoration: InputDecoration(
hintText: 'Enter your qualification',
border: InputBorder.none,
suffixIcon: IconButton(
icon: const Icon(Icons.add_circle_outline,
color: Colors.blue),
onPressed: _newQualification,
),
),
onFieldSubmitted: (_) => _newQualification(),
),
),
),
],
const SizedBox(height: 20),
],
if (qualifications.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
'Selected Qualifications',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
),
),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: qualifications.length,
padding: const EdgeInsets.symmetric(horizontal: 20),
itemBuilder: (context, index) {
return Card(
elevation: 0,
color: Colors.blue.withOpacity(0.1),
margin: const EdgeInsets.only(bottom: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
title: Text(
qualifications[index],
style: const TextStyle(
fontWeight: FontWeight.w500,
),
),
trailing: IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
size: 20,
),
onPressed: () => _removeQualification(index),
),
),
);
},
),
],
const SizedBox(height: 20),
],
),
),
),
),

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
import 'package:medora/route/route_names.dart';
class SpecialitiesScreen extends StatefulWidget {
final DoctorController controller;
@ -16,77 +16,223 @@ class SpecialitiesScreen extends StatefulWidget {
class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
String? selectedSpeciality;
late final DoctorController _controller;
late TextEditingController _speciality;
bool _isEditing = false;
final List<Map<String, dynamic>> specialities = [
{
'icon': Icons.child_care,
'label': 'Pediatric',
'value': 'pediatric',
'description': 'Specialist in child healthcare',
'value': 'Pediatric',
'description': 'Medical care for infants, children, and adolescents',
},
{
'icon': Icons.medical_services,
'label': 'Casual',
'value': 'casual',
'description': 'General healthcare provider',
'label': 'General Medicine',
'value': 'General Medicine',
'description':
'Primary healthcare for adults and general medical conditions',
},
{
'icon': Icons.coronavirus,
'label': 'Corona',
'value': 'corona',
'description': 'COVID-19 specialist',
'icon': Icons.family_restroom,
'label': 'Family Medicine',
'value': 'Family Medicine',
'description': 'Comprehensive healthcare for families and individuals',
},
{
'icon': Icons.favorite,
'label': 'Cardiologist',
'value': 'Cardiologist',
'description': 'Diagnosis and treatment of heart conditions',
},
{
'icon': Icons.psychology,
'label': 'Neurology',
'value': 'Neurology',
'description': 'Treatment of nervous system disorders',
},
{
'icon': Icons.local_hospital,
'label': 'Gastroenterology',
'value': 'Gastroenterology',
'description': 'Digestive system disorders and treatment',
},
{
'icon': Icons.face,
'label': 'Dermatologist',
'value': 'Dermatologist',
'description': 'Skin, hair, and nail conditions',
},
{
'icon': Icons.wheelchair_pickup,
'label': 'Orthopedic',
'value': 'Orthopedic',
'description': 'Musculoskeletal system and injury treatment',
},
{
'icon': Icons.remove_red_eye,
'label': 'Ophthalmology',
'value': 'Ophthalmology',
'description': 'Eye care and vision treatment',
},
{
'icon': Icons.hearing,
'label': 'ENT',
'value': 'ENT',
'description': 'Ear, nose, and throat specialist',
},
{
'icon': Icons.psychology_outlined,
'label': 'Psychiatry',
'value': 'Psychiatry',
'description': 'Mental health and behavioral disorders',
},
{
'icon': Icons.pregnant_woman,
'label': 'Gynecology',
'value': 'gynecology',
'description': 'Women\'s health specialist',
'value': 'Gynecology',
'description': "Women's health and reproductive care",
},
{
'icon': Icons.medical_services_outlined,
'label': 'Orthopedic',
'value': 'orthopedic',
'description': 'Bone and joint specialist',
'icon': Icons.water_drop,
'label': 'Urology',
'value': 'Urology',
'description': 'Urinary tract and male reproductive health',
},
{
'icon': Icons.remove_red_eye,
'label': 'Eye',
'value': 'eye',
'description': 'Eye care specialist',
'icon': Icons.biotech,
'label': 'Endocrinology',
'value': 'Endocrinology',
'description': 'Hormone and metabolic disorders',
},
{
'icon': Icons.psychology,
'label': 'Psychiatrist',
'value': 'psychiatrist',
'description': 'Mental health specialist',
'icon': Icons.bloodtype,
'label': 'Oncology',
'value': 'Oncology',
'description': 'Cancer diagnosis and treatment',
},
{
'icon': Icons.medical_information,
'icon': Icons.accessibility,
'label': 'Rheumatology',
'value': 'Rheumatology',
'description': 'Arthritis and autoimmune conditions',
},
{
'icon': Icons.air,
'label': 'Pulmonology',
'value': 'Pulmonology',
'description': 'Respiratory system disorders',
},
{
'icon': Icons.water,
'label': 'Nephrology',
'value': 'Nephrology',
'description': 'Kidney diseases and disorders',
},
{
'icon': Icons.cleaning_services,
'label': 'Dentistry',
'value': 'dental',
'description': 'Dental care specialist',
'value': 'Dentistry',
'description': 'Oral health and dental care',
},
{
'icon': Icons.person,
'label': 'General Medicine',
'value': 'general',
'description': 'General practitioner',
'icon': Icons.accessibility_new,
'label': 'Physical Therapy',
'value': 'Physical Therapy',
'description': 'Rehabilitation and physical medicine',
},
{
'icon': Icons.sports,
'label': 'Sports Medicine',
'value': 'Sports Medicine',
'description': 'Athletic injuries and performance',
},
{
'icon': Icons.sick,
'label': 'Allergy & Immunology',
'value': 'Allergy & Immunology',
'description': 'Allergies and immune system disorders',
},
{
'icon': Icons.healing,
'label': 'Pain Management',
'value': 'Pain Management',
'description': 'Chronic pain treatment',
},
{
'icon': Icons.bedtime,
'label': 'Sleep Medicine',
'value': 'Sleep Medicine',
'description': 'Sleep disorders and treatment',
},
{
'icon': Icons.elderly,
'label': 'Geriatrics',
'value': 'Geriatrics',
'description': 'Healthcare for elderly patients',
}
// {
// 'icon': Icons.child_care,
// 'label': 'Pediatric',
// 'value': 'Pediatric',
// 'description': 'Specialist in child healthcare',
// },
// {
// 'icon': Icons.medical_services,
// 'label': 'Casual',
// 'value': 'casual',
// 'description': 'General healthcare provider',
// },
// {
// 'icon': Icons.coronavirus,
// 'label': 'Corona',
// 'value': 'corona',
// 'description': 'COVID-19 specialist',
// },
// {
// 'icon': Icons.pregnant_woman,
// 'label': 'Gynecology',
// 'value': 'gynecology',
// 'description': 'Women\'s health specialist',
// },
// {
// 'icon': Icons.medical_services_outlined,
// 'label': 'Orthopedic',
// 'value': 'orthopedic',
// 'description': 'Bone and joint specialist',
// },
// {
// 'icon': Icons.remove_red_eye,
// 'label': 'Eye',
// 'value': 'eye',
// 'description': 'Eye care specialist',
// },
// {
// 'icon': Icons.psychology,
// 'label': 'Psychiatrist',
// 'value': 'psychiatrist',
// 'description': 'Mental health specialist',
// },
// {
// 'icon': Icons.medical_information,
// 'label': 'Dentistry',
// 'value': 'dental',
// 'description': 'Dental care specialist',
// },
// {
// 'icon': Icons.person,
// 'label': 'General Medicine',
// 'value': 'general',
// 'description': 'General practitioner',
// },
];
@override
void initState() {
super.initState();
_controller = widget.controller ?? DoctorController();
_controller = widget.controller;
_loadSavedData();
}
void _loadSavedData() {
final doctor = _controller.model;
_speciality = TextEditingController(text: doctor.speciality ?? '');
}
void _loadSavedData() {}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
@ -163,7 +309,6 @@ class _SpecialitiesScreenState extends State<SpecialitiesScreen> {
setState(() {
// _speciality = specialty['value'];
selectedSpeciality = specialty['value'];
_isEditing = true;
widget.controller.updateSpeciality(selectedSpeciality!);
});