diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index af1be1a..1682e3c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:icon="@mipmap/launcher_icon"> + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 573806c..f61e294 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png index 4903837..f61e294 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 8901017..494c15e 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 0000000..494c15e Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 966302e..86c2c78 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 0000000..86c2c78 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index ebaf5f3..796b155 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000..796b155 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 903b3c7..51d1554 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000..51d1554 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..ab98328 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #ffffff + \ No newline at end of file diff --git a/images/logo.jpeg b/images/logo.jpeg new file mode 100644 index 0000000..a6b68ec Binary files /dev/null and b/images/logo.jpeg differ diff --git a/images/logo.jpg b/images/logo.jpg deleted file mode 100644 index bbeff10..0000000 Binary files a/images/logo.jpg and /dev/null differ diff --git a/images/splash_screen.jpg b/images/splash_screen.jpg index e6813c6..c88cff2 100644 Binary files a/images/splash_screen.jpg and b/images/splash_screen.jpg differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index b4c2412..580ffe4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index f87a37c..c3db73d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 58b0fef..b49b3aa 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 8da8b13..2bd334d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 82e7051..aca8ddc 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 89beb4c..fbef02a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 5bb1c82..7ccb15f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 58b0fef..b49b3aa 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 2878ebe..84b18a4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index b9eee56..9c4ddcf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index b956a51..dd0ec70 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index 43d5f9a..5a5fb2d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index da90b0a..8475d4c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index 0b2769e..861b2d8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index b9eee56..9c4ddcf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 5c59ac8..f92fa5d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 573806c..f61e294 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index ebaf5f3..796b155 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index a65c720..52f9759 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 1ee79e9..057f71d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index face7f1..ecc82ae 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/data/models/consultation_booking.dart b/lib/data/models/consultation_booking.dart index 468a6f8..43c4fdd 100644 --- a/lib/data/models/consultation_booking.dart +++ b/lib/data/models/consultation_booking.dart @@ -4,7 +4,8 @@ enum PaymentStatus { pending, completed, failed, refunded } class Booking { final String id; - // final String doctorId; + final String doctorId; + final String profileImageUrl; final String doctorName; final String patientId; final String patientName; @@ -18,7 +19,8 @@ class Booking { Booking({ required this.id, - // required this.doctorId, + required this.doctorId, + required this.profileImageUrl, required this.doctorName, required this.patientId, required this.patientName, @@ -31,11 +33,11 @@ class Booking { required this.specialization, }); - // Convert Booking object to a map for Firestore Map toMap() { return { 'id': id, - // 'doctorId': doctorId, + 'doctorId': doctorId, + 'profileImageUrl': profileImageUrl, 'doctorName': doctorName, 'patientId': patientId, 'patientName': patientName, @@ -52,7 +54,8 @@ class Booking { factory Booking.fromMap(Map map) { return Booking( id: map['id'], - // doctorId: map['doctorId'], + doctorId: map['doctorId'], + profileImageUrl: map['profileImageUrl'], doctorName: map['doctorName'], patientId: map['patientId'], patientName: map['patientName'], @@ -68,7 +71,6 @@ class Booking { ); } - // Create a copy of Booking with modified fields Booking copyWith({ String? id, String? doctorId, @@ -85,7 +87,8 @@ class Booking { }) { return Booking( id: id ?? this.id, - // doctorId: doctorId ?? this.doctorId, + doctorId: doctorId ?? this.doctorId, + profileImageUrl: profileImageUrl, doctorName: doctorName ?? this.doctorName, patientId: patientId ?? this.patientId, patientName: patientName ?? this.patientName, diff --git a/lib/data/models/doctor.dart b/lib/data/models/doctor.dart index f0b357d..2b3713f 100644 --- a/lib/data/models/doctor.dart +++ b/lib/data/models/doctor.dart @@ -22,6 +22,7 @@ class Doctor { String? addressType; Doctor({ + this.profileImageUrl, this.addressType, this.achievements, this.profileImageUrl, @@ -69,6 +70,7 @@ class Doctor { }; static Doctor fromJson(Map json) => Doctor( + profileImageUrl: json['profileImageUrl'], achievements: List.from(json['achievements'] ?? []), profileImageUrl: json['profileImageUrl'], profileImage: json['profileImage'], diff --git a/lib/data/services/consultation_booking_service.dart b/lib/data/services/consultation_booking_service.dart index 723a1cc..c293eb3 100644 --- a/lib/data/services/consultation_booking_service.dart +++ b/lib/data/services/consultation_booking_service.dart @@ -5,6 +5,8 @@ class BookingService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; Future createBooking({ + required String doctorId, + required String profileImageUrl, required String doctorName, required String patientId, required String patientName, @@ -19,6 +21,7 @@ class BookingService { final booking = Booking( id: docRef.id, + doctorId: doctorId, doctorName: doctorName, patientId: patientId, patientName: patientName, @@ -29,6 +32,7 @@ class BookingService { paymentStatus: PaymentStatus.pending, createdAt: DateTime.now(), specialization: specialization, + profileImageUrl: profileImageUrl, ); docRef.set(booking.toMap()); diff --git a/lib/route/routes.dart b/lib/route/routes.dart index 22791d4..5f73931 100644 --- a/lib/route/routes.dart +++ b/lib/route/routes.dart @@ -141,6 +141,7 @@ final Map routes = { ModalRoute.of(context)!.settings.arguments as Map; return DoctorDetailsScreen( doctor: args['doctor'] as Doctor, + preloadedImage: args['imageProvider'] as ImageProvider?, ); }, RouteNames.consultationCenterScreen: (context) { diff --git a/lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart b/lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart index de5e7d2..f1e9102 100644 --- a/lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart +++ b/lib/screens/patient_screens/appoinment_bookings/consultation_booking_screen.dart @@ -254,7 +254,7 @@ class _ConsultationBookingScreenState extends State { ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( - widget.doctor.profileImage!, + widget.doctor.profileImageUrl!, width: 80, height: 80, fit: BoxFit.cover, @@ -454,6 +454,8 @@ class _ConsultationBookingScreenState extends State { } final bookingId = await bookingService.createBooking( + doctorId: widget.doctor.uid!, + profileImageUrl: widget.doctor.profileImageUrl!, doctorName: widget.doctor.firstName ?? 'Doctor', patientId: currentUser!.uid, patientName: patientName ?? 'Patient', diff --git a/lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart b/lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart index 4fb0c11..7ee76c7 100644 --- a/lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart +++ b/lib/screens/patient_screens/appoinment_bookings/consultation_time_screen.dart @@ -153,7 +153,7 @@ class _ConsultationTimeScreenState extends State { ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( - widget.doctor.profileImage!, + widget.doctor.profileImageUrl!, width: 60, height: 60, fit: BoxFit.cover, diff --git a/lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart b/lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart index f5a21f0..d03f98b 100644 --- a/lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart +++ b/lib/screens/patient_screens/appoinment_bookings/consultations_center_screen.dart @@ -144,7 +144,7 @@ class _ConsultationsCenterScreenState extends State { ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( - widget.doctor.profileImage!, + widget.doctor.profileImageUrl!, width: 80, height: 80, fit: BoxFit.cover, diff --git a/lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart b/lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart index 52d35e6..02e1b87 100644 --- a/lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart +++ b/lib/screens/patient_screens/appoinment_bookings/doctor_details_screen.dart @@ -2,15 +2,25 @@ 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 StatelessWidget { +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( @@ -44,7 +54,7 @@ class DoctorDetailsScreen extends StatelessWidget { Navigator.pushNamed( context, RouteNames.consultationCenterScreen, arguments: { - 'doctor': doctor, + 'doctor': widget.doctor, }); }, style: ElevatedButton.styleFrom( @@ -113,38 +123,21 @@ class DoctorDetailsScreen extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Image.network( - doctor.profileImage!, - width: 100, - height: 100, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Container( - width: 100, - height: 100, - color: Colors.grey[300], - child: - Icon(Icons.person, size: 50, color: Colors.grey[600]), - ); - }, - ), - ), + _buildDoctorImage(), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - doctor.firstName ?? '', + widget.doctor.firstName ?? '', style: GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.w600, ), ), Text( - doctor.speciality!, + widget.doctor.speciality!, style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey[600], @@ -158,7 +151,7 @@ class DoctorDetailsScreen extends StatelessWidget { const SizedBox(width: 4), Expanded( child: Text( - doctor.speciality!, + widget.doctor.speciality!, style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey[600], @@ -174,7 +167,7 @@ class DoctorDetailsScreen extends StatelessWidget { const SizedBox(width: 4), Expanded( child: Text( - doctor.city!, + widget.doctor.city!, style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey[600], @@ -188,7 +181,7 @@ class DoctorDetailsScreen extends StatelessWidget { Icon(Icons.star, size: 16, color: Colors.blue[400]), const SizedBox(width: 4), Text( - '${doctor.yearsOfExperience} Years', + '${widget.doctor.yearsOfExperience} Years', style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey[600], @@ -206,6 +199,53 @@ class DoctorDetailsScreen extends StatelessWidget { ); } + 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), @@ -232,21 +272,33 @@ class DoctorDetailsScreen extends StatelessWidget { ), const SizedBox(height: 8), Text( - doctor.profileDescription!, + widget.doctor.profileDescription!, style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey[600], ), - maxLines: 3, - overflow: TextOverflow.ellipsis, + maxLines: isDescriptionExpanded ? null : 3, + overflow: isDescriptionExpanded ? null : TextOverflow.ellipsis, ), - TextButton( - onPressed: () {}, - child: Text( - 'Read more', - style: GoogleFonts.poppins( - color: Colors.blue, - fontWeight: FontWeight.w500, + 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, + ), ), ), ), @@ -280,16 +332,13 @@ class DoctorDetailsScreen extends StatelessWidget { ), ), const SizedBox(height: 8), - ...doctor.qualifications!.map((qual) => Padding( - padding: const EdgeInsets.only(bottom: 4), - child: Text( - qual.toString(), - style: GoogleFonts.poppins( - fontSize: 14, - color: Colors.grey[600], - ), - ), - )), + 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 index 9743cf6..39ba33f 100644 --- a/lib/screens/patient_screens/appoinment_bookings/doctors_list_screen.dart +++ b/lib/screens/patient_screens/appoinment_bookings/doctors_list_screen.dart @@ -1,10 +1,9 @@ -import 'dart:io'; - 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; @@ -29,7 +28,7 @@ class _DoctorsListScreenState extends State { doctorsQuery = FirebaseFirestore.instance .collection('doctorprofiles') - .where('speciality', isEqualTo: widget.specialty.toLowerCase()); + .where('speciality', isEqualTo: widget.specialty); _searchController.addListener(_onSearchChanged); } @@ -139,8 +138,10 @@ class _DoctorsListScreenState extends State { stream: doctorsQuery.snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return const SliverFillRemaining( - child: Center(child: CircularProgressIndicator()), + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => _buildShimmerDoctorCard(), + ), ); } @@ -192,6 +193,67 @@ class _DoctorsListScreenState extends State { ); } + 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), @@ -212,11 +274,13 @@ class _DoctorsListScreenState extends State { color: Colors.transparent, child: InkWell( onTap: () { + precacheImage(NetworkImage(doctor.profileImageUrl!), context); Navigator.pushNamed( context, RouteNames.doctorDetailsScreen, arguments: { 'doctor': doctor, + 'imageProvider': NetworkImage(doctor.profileImageUrl!), }, ); }, @@ -224,20 +288,7 @@ class _DoctorsListScreenState extends State { padding: const EdgeInsets.all(16), child: Row( children: [ - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - image: DecorationImage( - image: doctor.profileImage!.startsWith('http') - ? NetworkImage(doctor.profileImage!) - : FileImage(File(doctor.profileImage!)) - as ImageProvider, - fit: BoxFit.cover, - ), - ), - ), + _buildDoctorImage(doctor), const SizedBox(width: 16), Expanded( child: Column( @@ -296,4 +347,41 @@ class _DoctorsListScreenState extends State { ), ); } + + 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/patient_dashboard/patient_home_screen.dart b/lib/screens/patient_screens/patient_dashboard/patient_home_screen.dart index bc04b16..165b283 100644 --- a/lib/screens/patient_screens/patient_dashboard/patient_home_screen.dart +++ b/lib/screens/patient_screens/patient_dashboard/patient_home_screen.dart @@ -302,6 +302,7 @@ class _PatientHomeScreenState extends State 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, @@ -320,6 +321,7 @@ class _PatientHomeScreenState extends State } Widget _consultationCard( + String? profileImageUrl, String name, String schedule, String speciality, @@ -348,30 +350,50 @@ class _PatientHomeScreenState extends State 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), + 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, + ), ), - child: const Icon( - Icons.person, - size: 36, - color: Colors.white, - ), - ), const SizedBox(width: 16), Expanded( child: Column( diff --git a/pubspec.yaml b/pubspec.yaml index daa0c2f..dca2948 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.3.0+5 +version: 1.3.5+6 environment: sdk: ^3.5.3 @@ -71,10 +71,14 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^4.0.0 flutter_launcher_icons: ^0.14.1 -flutter_icons: - android: true +flutter_launcher_icons: + android: "launcher_icon" ios: true - image_path: "images/logo.jpg" + remove_alpha_ios: true + image_path: "images/logo.jpeg" + min_sdk_android: 24 + adaptive_icon_background: "#ffffff" + adaptive_icon_foreground: "images/logo.jpeg" # For information on the generic Dart part of this file, see the @@ -98,6 +102,7 @@ flutter: - images/MaleDr.jpg.png - images/FemaleDr.jpg.png - images/splash_screen.jpg + - images/logo.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images