Booking physical consultation , bugs fixed and Profile picture adding using firebase storage is complete. Co-authored-by: Jipson George <152465898+Jipson-cosq@users.noreply.github.com> Reviewed-on: cosqnet/telemednet#6 Co-authored-by: DhanshCOSQ <dhanshas@cosq.net> Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
675 lines
20 KiB
Dart
675 lines
20 KiB
Dart
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:medora/data/models/consultation_booking.dart';
|
|
import 'package:medora/data/services/consultation_booking_service.dart';
|
|
import 'package:medora/route/route_names.dart';
|
|
|
|
import 'package:medora/screens/patientScreens/appoinmentBooking/speciality_screen.dart';
|
|
|
|
class PatientHomeScreen extends StatefulWidget {
|
|
const PatientHomeScreen({super.key});
|
|
|
|
@override
|
|
State<PatientHomeScreen> createState() => _PatientHomeScreenState();
|
|
}
|
|
|
|
class _PatientHomeScreenState extends State<PatientHomeScreen>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _animationController;
|
|
final BookingService _bookingService = BookingService();
|
|
late Stream<List<Booking>> _bookingsStream;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_animationController = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 300),
|
|
);
|
|
_animationController.forward();
|
|
|
|
final User? user = FirebaseAuth.instance.currentUser;
|
|
if (user != null) {
|
|
final String userId = user.uid;
|
|
_bookingsStream = _bookingService.getPatientBookings(userId);
|
|
} else {
|
|
_bookingsStream = const Stream.empty();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_animationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: Column(
|
|
children: [
|
|
_buildSearchBar(),
|
|
Expanded(
|
|
child: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
_buildRealTimeCard(),
|
|
const SizedBox(height: 20),
|
|
_buildConsultationsSection(),
|
|
const SizedBox(height: 20),
|
|
_buildFindDoctorSection(),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSearchBar() {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue,
|
|
borderRadius: const BorderRadius.only(
|
|
bottomLeft: Radius.circular(30.0),
|
|
bottomRight: Radius.circular(30.0),
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.blue.withOpacity(0.3),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(30),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: TextField(
|
|
decoration: InputDecoration(
|
|
hintText: 'Search Doctor/Hospital/Symptoms',
|
|
hintStyle: GoogleFonts.poppins(
|
|
color: Colors.grey[400],
|
|
),
|
|
prefixIcon: const Icon(Icons.search, color: Colors.blue),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(30),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRealTimeCard() {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Colors.blue[400]!, Colors.white],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.blue.withOpacity(0.3),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Real-time care\nat your fingertips.',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 30,
|
|
fontWeight: FontWeight.bold,
|
|
color: const Color.fromARGB(221, 67, 67, 67),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => const SpecialtyScreen()),
|
|
);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: Colors.blue[700],
|
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 7),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(30),
|
|
),
|
|
elevation: 5,
|
|
),
|
|
child: Text(
|
|
'Start Consultation',
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildConsultationsSection() {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Upcoming Consultations',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey[800],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
SizedBox(
|
|
height: 201,
|
|
child: StreamBuilder<List<Booking>>(
|
|
stream: _bookingsStream,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const CircularProgressIndicator(),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'Loading consultations...',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
if (snapshot.hasError) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.error_outline,
|
|
color: Colors.red[400], size: 48),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'Error loading consultations',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.red[400],
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
// Implement refresh logic
|
|
},
|
|
child: Text(
|
|
'Try Again',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.blue[700],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
final bookings = snapshot.data ?? [];
|
|
|
|
if (bookings.isEmpty) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.calendar_today,
|
|
color: Colors.grey[400], size: 48),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'No upcoming consultations',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.grey[600],
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
// Navigate to book consultation
|
|
},
|
|
child: Text(
|
|
'Book a Consultation',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.blue[700],
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return ListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: bookings.length,
|
|
itemBuilder: (context, index) {
|
|
final booking = bookings[index];
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: Hero(
|
|
tag: 'consultation_${booking.id}',
|
|
child: Material(
|
|
child: _consultationCard(
|
|
booking.doctorName,
|
|
'${DateFormat('EEE, MMM d, yyyy').format(booking.appointmentDate)}\n${booking.appointmentTime}',
|
|
booking.specialization,
|
|
booking.paymentStatus,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _consultationCard(
|
|
String name,
|
|
String schedule,
|
|
String speciality,
|
|
PaymentStatus paymentStatus,
|
|
) {
|
|
return Container(
|
|
width: 300,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Colors.white, Colors.grey[50]!],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(24),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.1),
|
|
blurRadius: 20,
|
|
offset: const Offset(0, 8),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Colors.blue[400]!, Colors.blue[600]!],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.blue[300]!.withOpacity(0.3),
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: const Icon(
|
|
Icons.person,
|
|
size: 36,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
name,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey[800],
|
|
),
|
|
),
|
|
Text(
|
|
speciality,
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.grey[600],
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: _getStatusColor(paymentStatus).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color:
|
|
_getStatusColor(paymentStatus).withOpacity(0.2),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
_getStatusIcon(paymentStatus),
|
|
size: 14,
|
|
color: _getStatusColor(paymentStatus),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
_getStatusText(paymentStatus),
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
color: _getStatusColor(paymentStatus),
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue[50],
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: Colors.blue[100]!,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.calendar_today,
|
|
size: 18,
|
|
color: Colors.blue[700],
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
schedule,
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.blue[700],
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
IconData _getStatusIcon(PaymentStatus status) {
|
|
switch (status) {
|
|
case PaymentStatus.completed:
|
|
return Icons.check_circle;
|
|
case PaymentStatus.pending:
|
|
return Icons.access_time;
|
|
case PaymentStatus.failed:
|
|
return Icons.error;
|
|
default:
|
|
return Icons.info;
|
|
}
|
|
}
|
|
|
|
Color _getStatusColor(PaymentStatus status) {
|
|
switch (status) {
|
|
case PaymentStatus.pending:
|
|
return Colors.orange;
|
|
case PaymentStatus.completed:
|
|
return Colors.green;
|
|
case PaymentStatus.failed:
|
|
return Colors.red;
|
|
default:
|
|
return Colors.grey;
|
|
}
|
|
}
|
|
|
|
String _getStatusText(PaymentStatus status) {
|
|
switch (status) {
|
|
case PaymentStatus.pending:
|
|
return 'Payment Pending';
|
|
case PaymentStatus.completed:
|
|
return 'Confirmed';
|
|
case PaymentStatus.failed:
|
|
return 'Payment Failed';
|
|
default:
|
|
return 'Unknown';
|
|
}
|
|
}
|
|
|
|
Widget _buildFindDoctorSection() {
|
|
final specialistData = [
|
|
{
|
|
'icon': Icons.local_hospital,
|
|
'label': 'General',
|
|
'color': Colors.blue,
|
|
'description': 'Primary Healthcare'
|
|
},
|
|
{
|
|
'icon': Icons.remove_red_eye,
|
|
'label': 'Eye',
|
|
'color': Colors.indigo,
|
|
'description': 'Vision Care'
|
|
},
|
|
{
|
|
'icon': Icons.medical_services,
|
|
'label': 'Dental',
|
|
'color': Colors.amber,
|
|
'description': 'Oral Health'
|
|
},
|
|
{
|
|
'icon': Icons.favorite,
|
|
'label': 'Cardio',
|
|
'color': Colors.red,
|
|
'description': 'Heart Specialist'
|
|
},
|
|
{
|
|
'icon': Icons.psychology,
|
|
'label': 'Mental',
|
|
'color': Colors.green,
|
|
'description': 'Mental Health'
|
|
},
|
|
{
|
|
'icon': Icons.child_care,
|
|
'label': 'Pediatric',
|
|
'color': Colors.purple,
|
|
'description': 'Child Care'
|
|
},
|
|
{
|
|
'icon': Icons.elderly,
|
|
'label': 'Geriatric',
|
|
'color': Colors.teal,
|
|
'description': 'Senior Care'
|
|
},
|
|
{
|
|
'icon': Icons.fitness_center,
|
|
'label': 'Physio',
|
|
'color': Colors.orange,
|
|
'description': 'Physical Therapy'
|
|
},
|
|
];
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
child: Text(
|
|
'Find Specialists',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: [
|
|
for (final data in specialistData)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
child: _specialistCard(
|
|
icon: data['icon'] as IconData,
|
|
label: data['label'] as String,
|
|
color: data['color'] as Color,
|
|
description: data['description'] as String,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.arrow_forward, color: Colors.blue),
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => const SpecialtyScreen()),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _specialistCard({
|
|
required IconData icon,
|
|
required String label,
|
|
required Color color,
|
|
required String description,
|
|
}) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
Navigator.pushNamed(
|
|
context,
|
|
RouteNames.doctorListScreen,
|
|
arguments: {
|
|
'specialty': label,
|
|
},
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 140,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: color.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(icon, color: color, size: 24),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
label,
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.black87,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
Text(
|
|
description,
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.grey[600],
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|