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>
@ -2,7 +2,7 @@
|
||||
<application
|
||||
android:label="Medora"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
@ -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>
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 7.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
4
android/app/src/main/res/values/colors.xml
Normal 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
|
After Width: | Height: | Size: 228 KiB |
BIN
images/logo.jpg
|
Before Width: | Height: | Size: 324 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 888 KiB |
|
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 645 B After Width: | Height: | Size: 452 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 956 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 956 B |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.7 KiB |
@ -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<String, dynamic> 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<String, dynamic> 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,
|
||||
|
||||
@ -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<String, dynamic> json) => Doctor(
|
||||
profileImageUrl: json['profileImageUrl'],
|
||||
achievements: List<String>.from(json['achievements'] ?? []),
|
||||
profileImageUrl: json['profileImageUrl'],
|
||||
profileImage: json['profileImage'],
|
||||
|
||||
@ -5,6 +5,8 @@ class BookingService {
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
|
||||
Future<String> 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());
|
||||
|
||||
@ -141,6 +141,7 @@ final Map<String, Widget Function(BuildContext)> routes = {
|
||||
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
|
||||
return DoctorDetailsScreen(
|
||||
doctor: args['doctor'] as Doctor,
|
||||
preloadedImage: args['imageProvider'] as ImageProvider?,
|
||||
);
|
||||
},
|
||||
RouteNames.consultationCenterScreen: (context) {
|
||||
|
||||
@ -254,7 +254,7 @@ class _ConsultationBookingScreenState extends State<ConsultationBookingScreen> {
|
||||
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<ConsultationBookingScreen> {
|
||||
}
|
||||
|
||||
final bookingId = await bookingService.createBooking(
|
||||
doctorId: widget.doctor.uid!,
|
||||
profileImageUrl: widget.doctor.profileImageUrl!,
|
||||
doctorName: widget.doctor.firstName ?? 'Doctor',
|
||||
patientId: currentUser!.uid,
|
||||
patientName: patientName ?? 'Patient',
|
||||
|
||||
@ -153,7 +153,7 @@ class _ConsultationTimeScreenState extends State<ConsultationTimeScreen> {
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.network(
|
||||
widget.doctor.profileImage!,
|
||||
widget.doctor.profileImageUrl!,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
|
||||
@ -144,7 +144,7 @@ class _ConsultationsCenterScreenState extends State<ConsultationsCenterScreen> {
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.network(
|
||||
widget.doctor.profileImage!,
|
||||
widget.doctor.profileImageUrl!,
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
|
||||
@ -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<DoctorDetailsScreen> createState() => _DoctorDetailsScreenState();
|
||||
}
|
||||
|
||||
class _DoctorDetailsScreenState extends State<DoctorDetailsScreen> {
|
||||
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],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -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<DoctorsListScreen> {
|
||||
|
||||
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<DoctorsListScreen> {
|
||||
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<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) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
@ -212,11 +274,13 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
|
||||
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<DoctorsListScreen> {
|
||||
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<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],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,6 +302,7 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
|
||||
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<PatientHomeScreen>
|
||||
}
|
||||
|
||||
Widget _consultationCard(
|
||||
String? profileImageUrl,
|
||||
String name,
|
||||
String schedule,
|
||||
String speciality,
|
||||
@ -348,30 +350,50 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
|
||||
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(
|
||||
|
||||
13
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
|
||||
|
||||