feature/medora-55 (#10)

Added Splash screen, logo and some minor changes in the code

Co-authored-by: Jipson George <152465898+Jipson-cosq@users.noreply.github.com>
Reviewed-on: cosqnet/telemednet#10
Co-authored-by: DhanshCOSQ <dhanshas@cosq.net>
Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
This commit is contained in:
Dhansh A S 2024-11-14 12:53:33 +00:00 committed by Benoy Bose
parent 520c9b6e44
commit 0f72ecf6ad
53 changed files with 290 additions and 101 deletions

View File

@ -2,7 +2,7 @@
<application <application
android:label="Medora" android:label="Medora"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/launcher_icon">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#ffffff</color>
</resources>

BIN
images/logo.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -4,7 +4,8 @@ enum PaymentStatus { pending, completed, failed, refunded }
class Booking { class Booking {
final String id; final String id;
// final String doctorId; final String doctorId;
final String profileImageUrl;
final String doctorName; final String doctorName;
final String patientId; final String patientId;
final String patientName; final String patientName;
@ -18,7 +19,8 @@ class Booking {
Booking({ Booking({
required this.id, required this.id,
// required this.doctorId, required this.doctorId,
required this.profileImageUrl,
required this.doctorName, required this.doctorName,
required this.patientId, required this.patientId,
required this.patientName, required this.patientName,
@ -31,11 +33,11 @@ class Booking {
required this.specialization, required this.specialization,
}); });
// Convert Booking object to a map for Firestore
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'id': id, 'id': id,
// 'doctorId': doctorId, 'doctorId': doctorId,
'profileImageUrl': profileImageUrl,
'doctorName': doctorName, 'doctorName': doctorName,
'patientId': patientId, 'patientId': patientId,
'patientName': patientName, 'patientName': patientName,
@ -52,7 +54,8 @@ class Booking {
factory Booking.fromMap(Map<String, dynamic> map) { factory Booking.fromMap(Map<String, dynamic> map) {
return Booking( return Booking(
id: map['id'], id: map['id'],
// doctorId: map['doctorId'], doctorId: map['doctorId'],
profileImageUrl: map['profileImageUrl'],
doctorName: map['doctorName'], doctorName: map['doctorName'],
patientId: map['patientId'], patientId: map['patientId'],
patientName: map['patientName'], patientName: map['patientName'],
@ -68,7 +71,6 @@ class Booking {
); );
} }
// Create a copy of Booking with modified fields
Booking copyWith({ Booking copyWith({
String? id, String? id,
String? doctorId, String? doctorId,
@ -85,7 +87,8 @@ class Booking {
}) { }) {
return Booking( return Booking(
id: id ?? this.id, id: id ?? this.id,
// doctorId: doctorId ?? this.doctorId, doctorId: doctorId ?? this.doctorId,
profileImageUrl: profileImageUrl,
doctorName: doctorName ?? this.doctorName, doctorName: doctorName ?? this.doctorName,
patientId: patientId ?? this.patientId, patientId: patientId ?? this.patientId,
patientName: patientName ?? this.patientName, patientName: patientName ?? this.patientName,

View File

@ -22,6 +22,7 @@ class Doctor {
String? addressType; String? addressType;
Doctor({ Doctor({
this.profileImageUrl,
this.addressType, this.addressType,
this.achievements, this.achievements,
this.profileImageUrl, this.profileImageUrl,
@ -69,6 +70,7 @@ class Doctor {
}; };
static Doctor fromJson(Map<String, dynamic> json) => Doctor( static Doctor fromJson(Map<String, dynamic> json) => Doctor(
profileImageUrl: json['profileImageUrl'],
achievements: List<String>.from(json['achievements'] ?? []), achievements: List<String>.from(json['achievements'] ?? []),
profileImageUrl: json['profileImageUrl'], profileImageUrl: json['profileImageUrl'],
profileImage: json['profileImage'], profileImage: json['profileImage'],

View File

@ -5,6 +5,8 @@ class BookingService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<String> createBooking({ Future<String> createBooking({
required String doctorId,
required String profileImageUrl,
required String doctorName, required String doctorName,
required String patientId, required String patientId,
required String patientName, required String patientName,
@ -19,6 +21,7 @@ class BookingService {
final booking = Booking( final booking = Booking(
id: docRef.id, id: docRef.id,
doctorId: doctorId,
doctorName: doctorName, doctorName: doctorName,
patientId: patientId, patientId: patientId,
patientName: patientName, patientName: patientName,
@ -29,6 +32,7 @@ class BookingService {
paymentStatus: PaymentStatus.pending, paymentStatus: PaymentStatus.pending,
createdAt: DateTime.now(), createdAt: DateTime.now(),
specialization: specialization, specialization: specialization,
profileImageUrl: profileImageUrl,
); );
docRef.set(booking.toMap()); docRef.set(booking.toMap());

View File

@ -141,6 +141,7 @@ final Map<String, Widget Function(BuildContext)> routes = {
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>; ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
return DoctorDetailsScreen( return DoctorDetailsScreen(
doctor: args['doctor'] as Doctor, doctor: args['doctor'] as Doctor,
preloadedImage: args['imageProvider'] as ImageProvider?,
); );
}, },
RouteNames.consultationCenterScreen: (context) { RouteNames.consultationCenterScreen: (context) {

View File

@ -254,7 +254,7 @@ class _ConsultationBookingScreenState extends State<ConsultationBookingScreen> {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Image.network( child: Image.network(
widget.doctor.profileImage!, widget.doctor.profileImageUrl!,
width: 80, width: 80,
height: 80, height: 80,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -454,6 +454,8 @@ class _ConsultationBookingScreenState extends State<ConsultationBookingScreen> {
} }
final bookingId = await bookingService.createBooking( final bookingId = await bookingService.createBooking(
doctorId: widget.doctor.uid!,
profileImageUrl: widget.doctor.profileImageUrl!,
doctorName: widget.doctor.firstName ?? 'Doctor', doctorName: widget.doctor.firstName ?? 'Doctor',
patientId: currentUser!.uid, patientId: currentUser!.uid,
patientName: patientName ?? 'Patient', patientName: patientName ?? 'Patient',

View File

@ -153,7 +153,7 @@ class _ConsultationTimeScreenState extends State<ConsultationTimeScreen> {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Image.network( child: Image.network(
widget.doctor.profileImage!, widget.doctor.profileImageUrl!,
width: 60, width: 60,
height: 60, height: 60,
fit: BoxFit.cover, fit: BoxFit.cover,

View File

@ -144,7 +144,7 @@ class _ConsultationsCenterScreenState extends State<ConsultationsCenterScreen> {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Image.network( child: Image.network(
widget.doctor.profileImage!, widget.doctor.profileImageUrl!,
width: 80, width: 80,
height: 80, height: 80,
fit: BoxFit.cover, fit: BoxFit.cover,

View File

@ -2,15 +2,25 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:medora/data/models/doctor.dart'; import 'package:medora/data/models/doctor.dart';
import 'package:medora/route/route_names.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 Doctor doctor;
final ImageProvider? preloadedImage;
const DoctorDetailsScreen({ const DoctorDetailsScreen({
super.key, super.key,
required this.doctor, required this.doctor,
this.preloadedImage,
}); });
@override
State<DoctorDetailsScreen> createState() => _DoctorDetailsScreenState();
}
class _DoctorDetailsScreenState extends State<DoctorDetailsScreen> {
bool isDescriptionExpanded = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -44,7 +54,7 @@ class DoctorDetailsScreen extends StatelessWidget {
Navigator.pushNamed( Navigator.pushNamed(
context, RouteNames.consultationCenterScreen, context, RouteNames.consultationCenterScreen,
arguments: { arguments: {
'doctor': doctor, 'doctor': widget.doctor,
}); });
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
@ -113,38 +123,21 @@ class DoctorDetailsScreen extends StatelessWidget {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ClipRRect( _buildDoctorImage(),
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]),
);
},
),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
doctor.firstName ?? '', widget.doctor.firstName ?? '',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
Text( Text(
doctor.speciality!, widget.doctor.speciality!,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey[600], color: Colors.grey[600],
@ -158,7 +151,7 @@ class DoctorDetailsScreen extends StatelessWidget {
const SizedBox(width: 4), const SizedBox(width: 4),
Expanded( Expanded(
child: Text( child: Text(
doctor.speciality!, widget.doctor.speciality!,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey[600], color: Colors.grey[600],
@ -174,7 +167,7 @@ class DoctorDetailsScreen extends StatelessWidget {
const SizedBox(width: 4), const SizedBox(width: 4),
Expanded( Expanded(
child: Text( child: Text(
doctor.city!, widget.doctor.city!,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey[600], color: Colors.grey[600],
@ -188,7 +181,7 @@ class DoctorDetailsScreen extends StatelessWidget {
Icon(Icons.star, size: 16, color: Colors.blue[400]), Icon(Icons.star, size: 16, color: Colors.blue[400]),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'${doctor.yearsOfExperience} Years', '${widget.doctor.yearsOfExperience} Years',
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey[600], 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() { Widget _buildDescription() {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -232,21 +272,33 @@ class DoctorDetailsScreen extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
doctor.profileDescription!, widget.doctor.profileDescription!,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 14, fontSize: 14,
color: Colors.grey[600], color: Colors.grey[600],
), ),
maxLines: 3, maxLines: isDescriptionExpanded ? null : 3,
overflow: TextOverflow.ellipsis, overflow: isDescriptionExpanded ? null : TextOverflow.ellipsis,
), ),
TextButton( Align(
onPressed: () {}, alignment: Alignment.topLeft,
child: Text( child: TextButton(
'Read more', onPressed: () {
style: GoogleFonts.poppins( setState(() {
color: Colors.blue, isDescriptionExpanded = !isDescriptionExpanded;
fontWeight: FontWeight.w500, });
},
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), const SizedBox(height: 8),
...doctor.qualifications!.map((qual) => Padding( Text(
padding: const EdgeInsets.only(bottom: 4), widget.doctor.qualifications?.join(', ') ?? '',
child: Text( style: GoogleFonts.poppins(
qual.toString(), fontSize: 14,
style: GoogleFonts.poppins( color: Colors.grey[600],
fontSize: 14, ),
color: Colors.grey[600], ),
),
),
)),
], ],
), ),
); );

View File

@ -1,10 +1,9 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:medora/data/models/doctor.dart'; import 'package:medora/data/models/doctor.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:medora/route/route_names.dart'; import 'package:medora/route/route_names.dart';
import 'package:shimmer/shimmer.dart';
class DoctorsListScreen extends StatefulWidget { class DoctorsListScreen extends StatefulWidget {
final String specialty; final String specialty;
@ -29,7 +28,7 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
doctorsQuery = FirebaseFirestore.instance doctorsQuery = FirebaseFirestore.instance
.collection('doctorprofiles') .collection('doctorprofiles')
.where('speciality', isEqualTo: widget.specialty.toLowerCase()); .where('speciality', isEqualTo: widget.specialty);
_searchController.addListener(_onSearchChanged); _searchController.addListener(_onSearchChanged);
} }
@ -139,8 +138,10 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
stream: doctorsQuery.snapshots(), stream: doctorsQuery.snapshots(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const SliverFillRemaining( return SliverList(
child: Center(child: CircularProgressIndicator()), delegate: SliverChildBuilderDelegate(
(context, index) => _buildShimmerDoctorCard(),
),
); );
} }
@ -192,6 +193,67 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
); );
} }
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) { Widget _buildDoctorCard(Doctor doctor) {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
@ -212,11 +274,13 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
precacheImage(NetworkImage(doctor.profileImageUrl!), context);
Navigator.pushNamed( Navigator.pushNamed(
context, context,
RouteNames.doctorDetailsScreen, RouteNames.doctorDetailsScreen,
arguments: { arguments: {
'doctor': doctor, 'doctor': doctor,
'imageProvider': NetworkImage(doctor.profileImageUrl!),
}, },
); );
}, },
@ -224,20 +288,7 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Row( child: Row(
children: [ children: [
Container( _buildDoctorImage(doctor),
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,
),
),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(
@ -296,4 +347,41 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
), ),
); );
} }
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],
),
);
},
),
),
);
}
} }

View File

@ -302,6 +302,7 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
tag: 'consultation_${booking.id}', tag: 'consultation_${booking.id}',
child: Material( child: Material(
child: _consultationCard( child: _consultationCard(
booking.profileImageUrl,
booking.doctorName, booking.doctorName,
'${DateFormat('EEE, MMM d, yyyy').format(booking.appointmentDate)}\n${booking.appointmentTime}', '${DateFormat('EEE, MMM d, yyyy').format(booking.appointmentDate)}\n${booking.appointmentTime}',
booking.specialization, booking.specialization,
@ -320,6 +321,7 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
} }
Widget _consultationCard( Widget _consultationCard(
String? profileImageUrl,
String name, String name,
String schedule, String schedule,
String speciality, String speciality,
@ -348,30 +350,50 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
children: [ children: [
Row( Row(
children: [ children: [
Container( if (profileImageUrl != null)
width: 64, Container(
height: 64, width: 64,
decoration: BoxDecoration( height: 64,
gradient: LinearGradient( decoration: BoxDecoration(
colors: [Colors.blue[400]!, Colors.blue[600]!], shape: BoxShape.circle,
begin: Alignment.topLeft, image: DecorationImage(
end: Alignment.bottomRight, image: NetworkImage(profileImageUrl),
), fit: BoxFit.cover,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.blue[300]!.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
), ),
], 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), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(

View File

@ -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 # 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 # 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. # 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: environment:
sdk: ^3.5.3 sdk: ^3.5.3
@ -71,10 +71,14 @@ dev_dependencies:
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
flutter_launcher_icons: ^0.14.1 flutter_launcher_icons: ^0.14.1
flutter_icons: flutter_launcher_icons:
android: true android: "launcher_icon"
ios: true 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 # For information on the generic Dart part of this file, see the
@ -98,6 +102,7 @@ flutter:
- images/MaleDr.jpg.png - images/MaleDr.jpg.png
- images/FemaleDr.jpg.png - images/FemaleDr.jpg.png
- images/splash_screen.jpg - images/splash_screen.jpg
- images/logo.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images # https://flutter.dev/to/resolution-aware-images