feature/medora-55 (#6)

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>
This commit is contained in:
Dhansh A S 2024-11-05 08:22:13 +00:00 committed by Benoy Bose
parent 266fca3bf7
commit b57523599c
88 changed files with 1460 additions and 554 deletions

3
.env
View File

@ -2,4 +2,5 @@ CUSTOM_SCHEME=com.cosqnet.telemednet
PROFILE_COLLECTION_NAME=telemednetusers
PATIENT_PROFILE_COLLECTION_NAME=patientprofiles
DOCTOR_PROFILE_COLLECTION_NAME=doctorprofiles
CONSULTATION_CENTER_COLLECTION_NAME=businesscenters
CONSULTATION_CENTER_COLLECTION_NAME=businesscenters
FIREBASE_STORAGE_BUCKET=gs://cosq-telemednet-dev.firebasestorage.app

6
.vscode/launch.json vendored
View File

@ -5,18 +5,18 @@
"version": "0.2.0",
"configurations": [
{
"name": "telemednet",
"name": "Medora",
"request": "launch",
"type": "dart"
},
{
"name": "telemednet (profile mode)",
"name": "Medora (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "telemednet (release mode)",
"name": "Medora (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release"

View File

@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="telemednet"
android:label="Medora"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

BIN
images/splash_screen.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

View File

@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";

View File

@ -1,122 +1 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,4 +1,4 @@
import 'package:telemednet/data/models/doctor.dart';
import 'package:medora/data/models/doctor.dart';
class ValidationHelper {
// Basic regex patterns
@ -92,16 +92,16 @@ class DoctorController {
model.middleName = middleName;
}
void updateLastName(String lastName) {
model.lastName = lastName;
void updateLastName(String firstName) {
model.firstName = firstName;
}
void addQualification(String qualification) {
model.qualifications!.add(qualification);
model.qualifications!.add(qualification.trim());
}
void removeQualification(String qualification) {
model.qualifications!.remove(qualification);
model.qualifications!.remove(qualification.trim());
}
void updateFloorBuilding(String floorBuilding) {

View File

@ -1,4 +1,4 @@
import 'package:telemednet/data/models/patient.dart';
import 'package:medora/data/models/patient.dart';
import '../data/services/patient_registration_service.dart';
class PatientController {
@ -95,7 +95,7 @@ class PatientController {
}
Future<bool> updatePatientData() async {
return await PatientProfileService.updatePatientProfile(this);
return await PatientProfileService.updatePatientProfile(model);
}
Future<bool> deletePatientData() async {

View File

@ -10,7 +10,7 @@ class Doctor {
String? title;
String? surName;
String? middleName;
String? lastName;
String? firstName;
List<String>? qualifications = [];
String? floorBuilding;
String? street;
@ -32,7 +32,7 @@ class Doctor {
this.title,
this.surName,
this.middleName,
this.lastName,
this.firstName,
this.qualifications,
this.floorBuilding,
this.street,
@ -53,7 +53,7 @@ class Doctor {
'digitalSignature': digitalSignature,
'title': title,
'surname': surName,
'lastName': lastName,
'firstName': firstName,
'middleName': middleName,
'qualifications': qualifications,
'floorBuilding': floorBuilding,
@ -76,7 +76,7 @@ class Doctor {
title: json['title'],
surName: json['surname'],
middleName: json['middleName'],
lastName: json['lastName'],
firstName: json['firstName'],
qualifications: List<String>.from(json['qualifications'] ?? []),
floorBuilding: json['floorBuilding'],
street: json['street'],

View File

@ -1,37 +0,0 @@
// import 'package:cloud_firestore/cloud_firestore.dart';
// class Doctors {
// final String name;
// final String location;
// final String specialization;
// final int experience;
// final String description;
// final List<String> qualification;
// final List<Map<String, dynamic>> consultation;
// final String profileImage;
// Doctors({
// required this.name,
// required this.location,
// required this.specialization,
// required this.experience,
// required this.description,
// required this.qualification,
// required this.consultation,
// required this.profileImage,
// });
// factory Doctors.fromFirestore(DocumentSnapshot doc) {
// final data = doc.data() as Map<String, dynamic>;
// return Doctors(
// name: data['name'] ?? '',
// location: data['location'] ?? '',
// specialization: data['specialization'] ?? '',
// experience: data['experience'] ?? 0,
// description: data['description'] ?? '',
// qualification: List<String>.from(data['qualification'] ?? []),
// consultation: List<Map<String, dynamic>>.from(data['consultation'] ?? []),
// profileImage: data['profile_image'] ?? '',
// );
// }
// }

View File

@ -4,6 +4,7 @@ class PatientModel {
String? gender;
DateTime? dateOfBirth;
String? profileImagePath;
String? profileImageUrl;
PatientAddress address;
List<FamilyMember> familyMembers = [];
@ -17,6 +18,7 @@ class PatientModel {
'gender': gender,
'dateOfBirth': dateOfBirth?.toIso8601String(),
'profileImagePath': profileImagePath,
'profileImageUrl': profileImageUrl,
'address': address.toJson(),
'familyMembers': familyMembers.map((member) => member.toJson()).toList(),
};
@ -31,15 +33,11 @@ class PatientModel {
? DateTime.parse(json['dateOfBirth'])
: null;
profileImagePath = json['profileImagePath'];
address.houseNo = json['houseNo'];
address.line = json['line'];
address.town = json['town'];
address.pincode = json['pincode'];
address.country = json['country'];
address.state = json['state'];
address.city = json['city'];
address.addressType = json['addressType'];
address.otherLabel = json['otherLabel'];
profileImageUrl = json['profileImageUrl'];
if (json['address'] != null) {
address =
PatientAddress.fromJson(json['address'] as Map<String, dynamic>);
}
if (json['familyMembers'] != null) {
familyMembers = (json['familyMembers'] as List)
.map((memberJson) => FamilyMember.fromJson(memberJson))
@ -53,15 +51,8 @@ class PatientModel {
gender = other.gender;
dateOfBirth = other.dateOfBirth;
profileImagePath = other.profileImagePath;
address.houseNo = other.address.houseNo;
address.line = other.address.line;
address.town = other.address.town;
address.pincode = other.address.pincode;
address.country = other.address.country;
address.state = other.address.state;
address.city = other.address.city;
address.addressType = other.address.addressType;
address.otherLabel = other.address.otherLabel;
profileImageUrl = other.profileImageUrl;
address = other.address;
familyMembers = other.familyMembers;
}
}

View File

@ -1,5 +1,5 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:telemednet/data/models/consultation_booking.dart';
import 'package:medora/data/models/consultation_booking.dart';
class BookingService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;

View File

@ -1,7 +1,7 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:telemednet/controllers/consultation_center_controller.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import '../models/consultation_center.dart';
class ConsultationCenterService {

View File

@ -1,7 +1,7 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:telemednet/data/models/telemed_user.dart';
import 'package:medora/data/models/telemed_user.dart';
class DataService {
static final String profileCollectionName =

View File

@ -1,8 +1,8 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import 'package:telemednet/data/models/doctor.dart';
import 'package:medora/controllers/doctor_controller.dart';
import 'package:medora/data/models/doctor.dart';
class DoctorProfileService {
static final String doctorProfileCollectionName =

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:telemednet/data/models/telemed_user.dart';
import 'package:telemednet/data/services/data_service.dart';
import 'package:telemednet/data/services/doctor_profile_service.dart';
import 'package:telemednet/data/services/patient_registration_service.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/data/models/telemed_user.dart';
import 'package:medora/data/services/data_service.dart';
import 'package:medora/data/services/doctor_profile_service.dart';
import 'package:medora/data/services/patient_registration_service.dart';
import 'package:medora/route/route_names.dart';
class NavigationService {
static Future<void> handleUserNavigation(BuildContext context) async {

View File

@ -1,13 +1,63 @@
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:telemednet/controllers/patient_controller.dart';
import 'package:telemednet/data/models/patient.dart';
import 'package:medora/controllers/patient_controller.dart';
import 'package:medora/data/models/patient.dart';
import 'package:path/path.dart' as path;
class PatientProfileService {
static final String patientProfileCollectionName =
dotenv.env['PATIENT_PROFILE_COLLECTION_NAME']!;
static final FirebaseFirestore db = FirebaseFirestore.instance;
static final FirebaseStorage storage = FirebaseStorage.instanceFor(
bucket: dotenv.env['FIREBASE_STORAGE_BUCKET']!);
static Future<String?> uploadProfileImage(File imageFile) async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user == null) {
print('No user logged in');
return null;
}
final String uid = user.uid;
final String fileName =
'profile_${uid}_${DateTime.now().millisecondsSinceEpoch}${path.extension(imageFile.path)}';
final Reference storageRef =
storage.ref().child('profile_images/$fileName');
final UploadTask uploadTask = storageRef.putFile(
imageFile,
SettableMetadata(
contentType: 'image/${path.extension(imageFile.path).substring(1)}',
customMetadata: {
'userId': uid,
'uploadedAt': DateTime.now().toIso8601String(),
},
),
);
final TaskSnapshot snapshot = await uploadTask;
final String downloadUrl = await snapshot.ref.getDownloadURL();
print('Profile image uploaded successfully');
return downloadUrl;
} catch (e) {
print('Error uploading profile image: $e');
return null;
}
}
static Future<bool> deleteProfileImage(String imageUrl) async {
try {
final Reference storageRef = storage.refFromURL(imageUrl);
await storageRef.delete();
print('Profile image deleted successfully');
return true;
} catch (e) {
print('Error deleting profile image: $e');
return false;
}
}
static Future<bool> savePatientProfile(PatientController controller) async {
try {
@ -19,11 +69,21 @@ class PatientProfileService {
final String uid = user.uid;
final PatientModel patientData = controller.model;
String? imageUrl;
if (patientData.profileImagePath != null) {
final File imageFile = File(patientData.profileImagePath!);
imageUrl = await uploadProfileImage(imageFile);
if (imageUrl == null) {
print('Failed to upload profile image');
return false;
}
}
final Map<String, dynamic> patientJson = patientData.toJson();
patientJson['createdAt'] = FieldValue.serverTimestamp();
patientJson['updatedAt'] = FieldValue.serverTimestamp();
patientJson['uid'] = uid;
patientJson['profileImageUrl'] = imageUrl;
await db
.collection(patientProfileCollectionName)
@ -38,32 +98,7 @@ class PatientProfileService {
}
}
static Future<PatientModel?> getPatientProfile() async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user == null) {
print('No user logged in');
return null;
}
final String uid = user.uid;
final DocumentSnapshot doc =
await db.collection(patientProfileCollectionName).doc(uid).get();
if (!doc.exists) {
print('No patient profile found for this user');
return null;
}
final data = doc.data() as Map<String, dynamic>;
return PatientModel.fromJson(data);
} catch (e) {
print('Error fetching patient profile: $e');
return null;
}
}
static Future<bool> updatePatientProfile(PatientController controller) async {
static Future<bool> updatePatientProfile(PatientModel patient) async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user == null) {
@ -72,10 +107,30 @@ class PatientProfileService {
}
final String uid = user.uid;
final PatientModel patientData = controller.model;
String? imageUrl;
if (patient.profileImagePath != null) {
final DocumentSnapshot oldDoc =
await db.collection(patientProfileCollectionName).doc(uid).get();
if (oldDoc.exists) {
final oldData = oldDoc.data() as Map<String, dynamic>;
final String? oldImageUrl = oldData['profileImageUrl'];
if (oldImageUrl != null) {
await deleteProfileImage(oldImageUrl);
}
}
final File imageFile = File(patient.profileImagePath!);
imageUrl = await uploadProfileImage(imageFile);
if (imageUrl == null) {
print('Failed to upload new profile image');
return false;
}
}
final Map<String, dynamic> patientJson = patientData.toJson();
final Map<String, dynamic> patientJson = patient.toJson();
patientJson['updatedAt'] = FieldValue.serverTimestamp();
if (imageUrl != null) {
patientJson['profileImageUrl'] = imageUrl;
}
await db
.collection(patientProfileCollectionName)
@ -99,6 +154,15 @@ class PatientProfileService {
}
final String uid = user.uid;
final DocumentSnapshot doc =
await db.collection(patientProfileCollectionName).doc(uid).get();
if (doc.exists) {
final data = doc.data() as Map<String, dynamic>;
final String? imageUrl = data['profileImageUrl'];
if (imageUrl != null) {
await deleteProfileImage(imageUrl);
}
}
await db.collection(patientProfileCollectionName).doc(uid).delete();
@ -109,4 +173,26 @@ class PatientProfileService {
return false;
}
}
static Future<PatientModel?> getPatientProfile() async {
try {
final User? user = FirebaseAuth.instance.currentUser;
if (user == null) {
print('No user logged in');
return null;
}
final String uid = user.uid;
final DocumentSnapshot doc =
await db.collection(patientProfileCollectionName).doc(uid).get();
if (!doc.exists) {
print('No patient profile found for this user');
return null;
}
final data = doc.data() as Map<String, dynamic>;
return PatientModel.fromJson(data);
} catch (e) {
print('Error fetching patient profile: $e');
return null;
}
}
}

View File

@ -1,8 +1,8 @@
import 'package:medora/telemednet_app.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:telemednet/telemednet_app.dart';
import 'firebase_options.dart';
void main() async {
@ -10,10 +10,7 @@ void main() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
PhoneAuthProvider()
]);
FirebaseUIAuth.configureProviders([EmailAuthProvider(), PhoneAuthProvider()]);
await dotenv.load();
runApp(const TelemednetApp());
}

View File

@ -40,4 +40,6 @@ class RouteNames {
static const String doctorServicesMenuScreen = '/doctor-services-menu';
static const String consultationTimeSlotScreen = '/doctor-timeslot';
static const String consultationDayScreen = '/doctor-consultation-days';
static const String loadingScreen = '/loading-screen';
static const String splashScreen = '/splash-screen';
}

View File

@ -1,51 +1,53 @@
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/consultation_center_controller.dart';
import 'package:telemednet/data/models/consultation_center.dart';
import 'package:telemednet/data/models/doctor.dart';
import 'package:telemednet/screens/authentication/launch_screen.dart';
// import 'package:telemednet/data/telemed_user.dart';
import 'package:telemednet/controllers/patient_controller.dart';
import 'package:telemednet/route_names.dart';
import 'package:telemednet/screens/doctorScreens/doctorConsultationSchedule/business_center_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorConsultationSchedule/center_fee_and_duration_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorConsultationSchedule/consultation_day_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorConsultationSchedule/consultation_schedule.dart';
import 'package:telemednet/screens/doctorScreens/doctorConsultationSchedule/consultation_time_slot_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_dashboard_home_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_dashboard_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_landing_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_personal_profile_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_services_menu_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/doctor_profile_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/qualifications_screen.dart';
import 'package:telemednet/screens/patientScreens/appoinmentBooking/consultation_booking_screen.dart';
import 'package:telemednet/screens/patientScreens/appoinmentBooking/consultation_time_screen.dart';
import 'package:telemednet/screens/patientScreens/appoinmentBooking/consultations_center_screen.dart';
import 'package:telemednet/screens/patientScreens/appoinmentBooking/doctor_details_screen.dart';
import 'package:telemednet/screens/patientScreens/appoinmentBooking/doctors_list_screen.dart';
import 'package:telemednet/screens/patientScreens/appoinmentBooking/speciality_screen.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:medora/screens/authentication/launch_screen.dart';
// import 'package:medora/data/telemed_user.dart';
import 'package:medora/controllers/patient_controller.dart';
import 'package:medora/route/route_names.dart';
import 'package:medora/screens/common/loading_screen.dart';
import 'package:medora/screens/doctorScreens/doctorConsultationSchedule/business_center_screen.dart';
import 'package:medora/screens/doctorScreens/doctorConsultationSchedule/center_fee_and_duration_screen.dart';
import 'package:medora/screens/doctorScreens/doctorConsultationSchedule/consultation_day_screen.dart';
import 'package:medora/screens/doctorScreens/doctorConsultationSchedule/consultation_schedule.dart';
import 'package:medora/screens/doctorScreens/doctorConsultationSchedule/consultation_time_slot_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_dashboard_home_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_dashboard_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_landing_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_personal_profile_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_services_menu_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/doctor_profile_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/qualifications_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/consultation_booking_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/consultation_time_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/consultations_center_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/doctor_details_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/doctors_list_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/speciality_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/achivements_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/address_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/achivements_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/address_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/digital_signature_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/digital_signature_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/experience_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/profile_description_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/experience_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/profile_description_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorProfileScreens/specialities_selection_screen.dart';
import 'package:telemednet/screens/patientScreens/patientDashboard/patient_dashboard_screen.dart';
import 'package:telemednet/screens/patientScreens/patientDashboard/patient_home_screen.dart';
import 'package:medora/screens/doctorScreens/doctorProfileScreens/specialities_selection_screen.dart';
import 'package:medora/screens/patientScreens/patientDashboard/patient_dashboard_screen.dart';
import 'package:medora/screens/patientScreens/patientDashboard/patient_home_screen.dart';
import 'package:telemednet/screens/patientScreens/patientDashboard/patient_profile_screen.dart';
import 'package:telemednet/screens/patientScreens/registrationScreens/patient_adress_screen.dart';
import 'package:telemednet/screens/patientScreens/registrationScreens/patient_family_members_screen.dart';
import 'package:telemednet/screens/patientScreens/registrationScreens/patient_registration_screen.dart';
import 'package:medora/screens/patientScreens/patientDashboard/patient_profile_screen.dart';
import 'package:medora/screens/patientScreens/registrationScreens/patient_adress_screen.dart';
import 'package:medora/screens/patientScreens/registrationScreens/patient_family_members_screen.dart';
import 'package:medora/screens/patientScreens/registrationScreens/patient_registration_screen.dart';
import 'package:medora/screens/splash_screen.dart';
import 'controllers/doctor_controller.dart';
import 'screens/patientScreens/patient_landing_screen.dart';
import 'screens/patientScreens/registrationScreens/family_members_edit_screen.dart';
import '../controllers/doctor_controller.dart';
import '../screens/patientScreens/patient_landing_screen.dart';
import '../screens/patientScreens/registrationScreens/family_members_edit_screen.dart';
final Map<String, Widget Function(BuildContext)> routes = {
RouteNames.launch: (context) => const LaunchScreen(),
@ -208,4 +210,6 @@ final Map<String, Widget Function(BuildContext)> routes = {
controller: controller ?? ConsultationCenterController(),
);
},
RouteNames.loadingScreen: (context) => const LoadingScreen(),
RouteNames.splashScreen: (context) => const SplashScreen(),
};

View File

@ -1,10 +1,9 @@
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/data/services/navigation_service.dart';
import 'package:telemednet/route_names.dart';
import 'package:telemednet/screens/authentication/sign_up_screen.dart';
import 'package:telemednet/widgets/primary_button.dart';
import 'package:medora/data/services/navigation_service.dart';
import 'package:medora/route/route_names.dart';
import 'package:medora/screens/authentication/sign_up_screen.dart';
import 'package:medora/widgets/primary_button.dart';
class LaunchScreen extends StatefulWidget {
const LaunchScreen({super.key});
@ -19,6 +18,17 @@ class _LaunchScreenState extends State<LaunchScreen> {
@override
void initState() {
super.initState();
_checkAuthenticationState();
}
void _checkAuthenticationState() {
FirebaseAuth.instance.authStateChanges().listen((user) {
if (user != null) {
if (mounted) {
_fetchProfileAndNavigate(context);
}
}
});
}
@override
@ -41,40 +51,7 @@ class _LaunchScreenState extends State<LaunchScreen> {
),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildLoadingWidget();
} else if (snapshot.hasData) {
_fetchProfileAndNavigate(context);
return _buildLoadingWidget();
} else {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Register',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
const Text(
'Who are you?',
style: TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
const SizedBox(height: 24),
_buildUserTypeSelection(context),
],
);
}
},
),
child: _buildUserTypeSelection(context),
),
),
),
@ -83,26 +60,26 @@ class _LaunchScreenState extends State<LaunchScreen> {
);
}
Widget _buildLoadingWidget() {
return const SizedBox(
height: 120,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 12),
Text('Please wait...'),
],
),
),
);
}
Widget _buildUserTypeSelection(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Register',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
const Text(
'Who are you?',
style: TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
const SizedBox(height: 24),
_buildSelectionCard(
title: 'Doctor',
description: 'Can organise and approve appointments',
@ -122,15 +99,7 @@ class _LaunchScreenState extends State<LaunchScreen> {
SizedBox(
width: double.infinity,
child: PrimaryButton(
onPressed: selectedUserType != null
? () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SignUpScreen(
selectedUserType: selectedUserType!,
),
),
)
: null,
onPressed: selectedUserType != null ? _navigateToSignUp : null,
text: 'Next',
),
),
@ -213,9 +182,17 @@ class _LaunchScreenState extends State<LaunchScreen> {
);
}
void _navigateToSignUp() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SignUpScreen(
selectedUserType: selectedUserType!,
),
),
);
}
Future<void> _fetchProfileAndNavigate(BuildContext context) async {
if (mounted) {
await NavigationService.handleUserNavigation(context);
}
await NavigationService.handleUserNavigation(context);
}
}

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:telemednet/data/services/data_service.dart';
import 'package:telemednet/data/services/navigation_service.dart';
import 'package:telemednet/widgets/primary_button.dart';
import 'package:medora/data/services/data_service.dart';
import 'package:medora/data/services/navigation_service.dart';
import 'package:medora/widgets/primary_button.dart';
class SignUpScreen extends StatefulWidget {
final String selectedUserType;

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
class LoadingScreen extends StatelessWidget {
const LoadingScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 32),
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue.shade700),
strokeWidth: 4,
),
const SizedBox(height: 24),
Text(
'Loading...',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blue.shade700,
),
),
const SizedBox(height: 8),
Text(
'Please wait while we fetch you...',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 32),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: LinearProgressIndicator(
value: null,
backgroundColor: Colors.grey.shade200,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue.shade700),
),
),
],
),
),
);
}
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/consultation_center_controller.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import '../../../route_names.dart';
import '../../../route/route_names.dart';
class BusinessCenterScreen extends StatefulWidget {
final ConsultationCenterController controller;

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:telemednet/controllers/consultation_center_controller.dart';
import 'package:telemednet/data/services/consultation_center_service.dart';
import 'package:telemednet/screens/doctorScreens/doctorConsultationSchedule/consultation_day_screen.dart';
import '../../../route_names.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import 'package:medora/data/services/consultation_center_service.dart';
import 'package:medora/screens/doctorScreens/doctorConsultationSchedule/consultation_day_screen.dart';
import '../../../route/route_names.dart';
class CenterFeeAndDurationScreen extends StatefulWidget {
final ConsultationCenterController controller;

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/consultation_center_controller.dart';
import 'package:telemednet/data/models/consultation_center.dart';
import 'package:telemednet/data/services/consultation_center_service.dart';
import 'package:telemednet/route_names.dart';
import 'package:telemednet/screens/doctorScreens/doctorConsultationSchedule/consultation_time_slot_screen.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/services/consultation_center_service.dart';
import 'package:medora/route/route_names.dart';
import 'package:medora/screens/doctorScreens/doctorConsultationSchedule/consultation_time_slot_screen.dart';
class ConsultationDayScreen extends StatefulWidget {
final ConsultationCenterController controller;

View File

@ -1,11 +1,11 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:telemednet/data/models/consultation_center.dart';
import 'package:telemednet/data/services/consultation_center_service.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/services/consultation_center_service.dart';
import '../../../common/custom_style.dart';
import '../../../route_names.dart';
import '../../../route/route_names.dart';
class ScheduleConsultationScreen extends StatefulWidget {
const ScheduleConsultationScreen({super.key});

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/consultation_center_controller.dart';
import 'package:telemednet/data/models/consultation_center.dart';
import 'package:medora/controllers/consultation_center_controller.dart';
import 'package:medora/data/models/consultation_center.dart';
class ConsultationTimeSlotScreen extends StatefulWidget {
final ConsultationCenterController controller;

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:telemednet/screens/patientScreens/appoinmentBooking/speciality_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/speciality_screen.dart';
class DoctorDashboardHomeScreen extends StatefulWidget {
const DoctorDashboardHomeScreen({super.key});

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_dashboard_home_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_personal_profile_screen.dart';
import 'package:telemednet/screens/doctorScreens/doctorDashboard/doctor_services_menu_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_dashboard_home_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_personal_profile_screen.dart';
import 'package:medora/screens/doctorScreens/doctorDashboard/doctor_services_menu_screen.dart';
class DoctorDashboardScreen extends StatefulWidget {
const DoctorDashboardScreen({super.key});

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/route/route_names.dart';
class DoctorLandingScreen extends StatelessWidget {
const DoctorLandingScreen({super.key});

View File

@ -1,6 +1,6 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/route/route_names.dart';
class DoctorPersonalProfileScreen extends StatefulWidget {
const DoctorPersonalProfileScreen({super.key});

View File

@ -1,6 +1,6 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/route/route_names.dart';
class DoctorServicesMenuScreen extends StatefulWidget {
const DoctorServicesMenuScreen({super.key});

View File

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

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route_names.dart';
import '../../../route/route_names.dart';
class DoctorAddressScreen extends StatefulWidget {
final DoctorController? controller;

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../data/services/doctor_profile_service.dart';
import '../../../route_names.dart';
import '../../../route/route_names.dart';
class DigitalSignatureScreen extends StatefulWidget {
final DoctorController controller;

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/controllers/doctor_controller.dart';
import 'package:medora/route/route_names.dart';
class ProfileUploadPage extends StatefulWidget {
const ProfileUploadPage({super.key});
@ -15,7 +15,7 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
final DoctorController _controller = DoctorController();
final TextEditingController _titleController = TextEditingController();
final TextEditingController _surnameController = TextEditingController();
final TextEditingController _lastnameController = TextEditingController();
final TextEditingController _firstController = TextEditingController();
final TextEditingController _middlenameController = TextEditingController();
File? _image;
final ImagePicker _picker = ImagePicker();
@ -31,7 +31,7 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
_titleController.text = _controller.model.title ?? '';
_surnameController.text = _controller.model.surName ?? '';
_lastnameController.text = _controller.model.lastName ?? '';
_firstController.text = _controller.model.firstName ?? '';
_middlenameController.text = _controller.model.middleName ?? '';
}
@ -39,7 +39,7 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
void dispose() {
_titleController.dispose();
_surnameController.dispose();
_lastnameController.dispose();
_firstController.dispose();
_middlenameController.dispose();
super.dispose();
}
@ -65,7 +65,7 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
if (_formKey.currentState!.validate()) {
_controller.updateTitle(_titleController.text);
_controller.updateSurName(_surnameController.text);
_controller.updateLastName(_lastnameController.text);
_controller.updateLastName(_firstController.text);
_controller.updateMiddleName(_middlenameController.text);
return true;
}
@ -242,6 +242,7 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
children: [
_buildUniformField(
label: 'Name',
icon: Icons.person,
child:
Container(), // The child parameter is not used in this implementation
@ -355,7 +356,7 @@ class _ProfileUploadPageState extends State<ProfileUploadPage> {
),
Expanded(
child: TextFormField(
controller: _lastnameController,
controller: _firstController,
onChanged: (value) {
_controller.updateLastName(value);
},

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route_names.dart';
import '../../../route/route_names.dart';
class ExperienceScreen extends StatefulWidget {
final DoctorController controller;
@ -180,7 +180,7 @@ class _ExperienceScreenState extends State<ExperienceScreen> {
if (value.length < 5) {
return 'License number must be at least 5 digits';
}
if (!RegExp(r'^[0-9]+$').hasMatch(value)) {
if (!RegExp(r'^[a-zA-Z0-9]+$').hasMatch(value)) {
return 'Please enter numbers only';
}
return null;

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import '../../../route_names.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
class ProfileDescriptionScreen extends StatefulWidget {
final DoctorController? controller;

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import '../../../route_names.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
class QualificationsScreen extends StatefulWidget {
final DoctorController? controller;
@ -82,12 +82,26 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
if (_formKey.currentState!.validate() &&
_validateQualification(qualification)) {
setState(() {
qualifications.add(qualification);
_isEditing = true;
_qualificationsController.clear();
});
_controller.addQualification(qualification);
// Check if qualification already exists (case-insensitive)
bool isDuplicate = qualifications
.any((q) => q.toLowerCase() == qualification.toLowerCase());
if (!isDuplicate) {
setState(() {
qualifications.add(qualification);
_isEditing = true;
_qualificationsController.clear();
});
_controller.addQualification(qualification);
} else {
// Show error message for duplicate qualification
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('This qualification has already been added'),
backgroundColor: Colors.red,
),
);
}
}
}
@ -102,8 +116,22 @@ class _QualificationsScreenState extends State<QualificationsScreen> {
qualifications.remove(qualification);
_controller.removeQualification(qualification);
} else {
qualifications.add(qualification);
_controller.addQualification(qualification);
// Check if qualification already exists (case-insensitive)
bool isDuplicate = qualifications
.any((q) => q.toLowerCase() == qualification.toLowerCase());
if (!isDuplicate) {
qualifications.add(qualification);
_controller.addQualification(qualification);
} else {
// Show error message for duplicate qualification
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('This qualification has already been added'),
backgroundColor: Colors.red,
),
);
}
}
});
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/doctor_controller.dart';
import '../../../route_names.dart';
import 'package:medora/controllers/doctor_controller.dart';
import '../../../route/route_names.dart';
class SpecialitiesScreen extends StatefulWidget {
final DoctorController controller;

View File

@ -2,13 +2,15 @@ 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:telemednet/data/models/consultation_center.dart';
import 'package:telemednet/data/models/doctor.dart';
import 'package:telemednet/data/services/consultation_booking_service.dart';
import 'package:telemednet/data/services/patient_registration_service.dart';
import 'package:telemednet/widgets/alert_screen.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:medora/data/models/patient.dart';
import 'package:medora/data/services/consultation_booking_service.dart';
import 'package:medora/data/services/patient_registration_service.dart';
import 'package:medora/route/route_names.dart';
import 'package:medora/widgets/alert_screen.dart';
class ConsultationBookingScreen extends StatelessWidget {
class ConsultationBookingScreen extends StatefulWidget {
final Doctor doctor;
final ConsultationCenter selectedConsultation;
final DateTime selectedDate;
@ -21,13 +23,58 @@ class ConsultationBookingScreen extends StatelessWidget {
required this.selectedDate,
required this.selectedTime,
});
@override
State<ConsultationBookingScreen> createState() =>
_ConsultationBookingScreenState();
}
class _ConsultationBookingScreenState extends State<ConsultationBookingScreen> {
PatientModel? selectedPatient;
List<PatientModel> familyMembers = [];
FamilyMember? selectedFamilyMember;
bool isLoading = true;
final TextEditingController _nameController = TextEditingController();
final TextEditingController _relationController = TextEditingController();
DateTime? _selectedDateOfBirth;
String _selectedGender = 'Male';
@override
void dispose() {
_nameController.dispose();
_relationController.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_loadPatientProfile();
}
Future<void> _loadPatientProfile() async {
setState(() => isLoading = true);
try {
final currentPatient = await PatientProfileService.getPatientProfile();
if (currentPatient != null) {
setState(() {
selectedPatient = currentPatient;
});
}
} catch (e) {
print('Error loading patient data: $e');
} finally {
setState(() => isLoading = false);
}
}
String get formattedAddress {
final parts = [
selectedConsultation.floorBuilding,
selectedConsultation.street,
selectedConsultation.city,
selectedConsultation.state,
selectedConsultation.postalCode
widget.selectedConsultation.floorBuilding,
widget.selectedConsultation.street,
widget.selectedConsultation.city,
widget.selectedConsultation.state,
widget.selectedConsultation.postalCode
].where((part) => part != null && part.isNotEmpty).toList();
return parts.join(', ');
}
@ -51,6 +98,8 @@ class ConsultationBookingScreen extends StatelessWidget {
const SizedBox(height: 24),
_buildPaymentDetails(),
const SizedBox(height: 24),
_buildInClinicAppointmentText(),
const SizedBox(height: 24),
_buildConfirmButton(context),
],
),
@ -59,6 +108,63 @@ class ConsultationBookingScreen extends StatelessWidget {
);
}
Widget _buildInClinicAppointmentText() {
String patientName =
selectedFamilyMember?.name ?? selectedPatient?.name ?? 'Select Patient';
String relation = selectedFamilyMember?.relation != null
? ' (${selectedFamilyMember!.relation})'
: '';
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: RichText(
text: TextSpan(
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.black87,
),
children: [
const TextSpan(text: 'In-clinic appointment for '),
TextSpan(
text: '$patientName$relation',
style: const TextStyle(
fontWeight: FontWeight.w600,
),
),
],
),
),
),
TextButton(
onPressed: _showPatientSelectionDialog,
child: Text(
'Change',
style: GoogleFonts.poppins(
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
}
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.white,
@ -100,7 +206,7 @@ class ConsultationBookingScreen extends StatelessWidget {
const Icon(Icons.calendar_today, color: Colors.white),
const SizedBox(width: 12),
Text(
DateFormat('EEEE, MMMM d').format(selectedDate),
DateFormat('EEEE, MMMM d').format(widget.selectedDate),
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
@ -115,7 +221,7 @@ class ConsultationBookingScreen extends StatelessWidget {
const Icon(Icons.access_time, color: Colors.white),
const SizedBox(width: 12),
Text(
selectedTime,
widget.selectedTime,
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
@ -147,8 +253,8 @@ class ConsultationBookingScreen extends StatelessWidget {
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
doctor.profileImage!,
child: Image.network(
widget.doctor.profileImage!,
width: 80,
height: 80,
fit: BoxFit.cover,
@ -168,21 +274,21 @@ class ConsultationBookingScreen extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
doctor.lastName!,
widget.doctor.firstName ?? '',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
Text(
doctor.speciality!,
widget.doctor.speciality!,
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
Text(
'${doctor.yearsOfExperience} years experience',
'${widget.doctor.yearsOfExperience} years experience',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
@ -230,7 +336,7 @@ class ConsultationBookingScreen extends StatelessWidget {
),
const SizedBox(height: 8),
Text(
'Average consultation time: ${selectedConsultation.averageDurationMinutes} minutes',
'Average consultation time: ${widget.selectedConsultation.averageDurationMinutes} minutes',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
@ -277,7 +383,7 @@ class ConsultationBookingScreen extends StatelessWidget {
),
),
Text(
'${selectedConsultation.consultationFee ?? "500"}',
'${widget.selectedConsultation.consultationFee ?? "500"}',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
@ -318,14 +424,24 @@ class ConsultationBookingScreen extends StatelessWidget {
}
void _showConfirmationDialog(BuildContext context) async {
final bookingService = BookingService();
final currentUser = FirebaseAuth.instance.currentUser;
final patientProfile = await PatientProfileService.getPatientProfile();
if (patientProfile == null) {
if (selectedPatient == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please select a patient for the appointment'),
backgroundColor: Colors.red,
),
);
return;
}
final bookingService = BookingService();
final currentUser = FirebaseAuth.instance.currentUser;
// Get the correct patient name based on selection
final patientName = selectedFamilyMember != null
? selectedFamilyMember!.name
: selectedPatient!.name;
try {
if (context.mounted) {
showDialog(
@ -338,15 +454,15 @@ class ConsultationBookingScreen extends StatelessWidget {
}
final bookingId = await bookingService.createBooking(
doctorName: doctor.lastName!,
doctorName: widget.doctor.firstName ?? 'Doctor',
patientId: currentUser!.uid,
patientName: patientProfile.name ?? 'Patient',
patientName: patientName ?? 'Patient',
location: formattedAddress,
appointmentDate: selectedDate,
appointmentTime: selectedTime,
appointmentDate: widget.selectedDate,
appointmentTime: widget.selectedTime,
consultationFee:
int.parse(selectedConsultation.consultationFee ?? "500"),
specialization: doctor.speciality!,
int.parse(widget.selectedConsultation.consultationFee ?? "500"),
specialization: widget.doctor.speciality!,
);
if (context.mounted) {
@ -358,11 +474,12 @@ class ConsultationBookingScreen extends StatelessWidget {
arguments: AlertArguments(
title: 'Booking Confirmed',
message:
'Your appointment has been successfully booked. Booking ID: ${bookingId.substring(0, 8)}\n\nPlease complete the payment to confirm your appointment.',
'Your in-clinic appointment has been successfully booked for $patientName. Booking ID: ${bookingId.substring(0, 8)}\n\nPlease complete the payment to confirm your appointment.',
actionTitle: 'View Appointments',
type: AlertType.success,
onActionPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
Navigator.pushReplacementNamed(
context, RouteNames.patientDashboardScreen);
},
),
),
@ -391,4 +508,333 @@ class ConsultationBookingScreen extends StatelessWidget {
}
}
}
Future<void> _showAddFamilyMemberDialog() async {
_nameController.clear();
_relationController.clear();
setState(() {
_selectedDateOfBirth = null;
_selectedGender = 'Male';
});
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) => StatefulBuilder(
builder: (BuildContext context, StateSetter setDialogState) {
return AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Text(
'Add Family Member',
style: GoogleFonts.poppins(fontWeight: FontWeight.w600),
),
content: AnimatedContainer(
duration: const Duration(milliseconds: 300),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Full Name',
labelStyle: GoogleFonts.poppins(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(
Icons.person_outline,
color: Colors.blue,
),
),
),
const SizedBox(height: 16),
TextFormField(
controller: _relationController,
decoration: InputDecoration(
labelText: 'Relation',
labelStyle: GoogleFonts.poppins(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(
Icons.family_restroom,
color: Colors.blue,
),
),
),
const SizedBox(height: 16),
InkWell(
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: ColorScheme.light(
primary: Colors.blue,
onPrimary: Colors.white,
surface: Colors.grey[100]!,
),
),
child: child!,
);
},
);
if (picked != null) {
setDialogState(() {
_selectedDateOfBirth = picked;
});
}
},
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(Icons.calendar_today,
color: Colors.blue),
const SizedBox(width: 12),
Text(
_selectedDateOfBirth != null
? DateFormat('dd/MM/yyyy')
.format(_selectedDateOfBirth!)
: 'Select Date of Birth',
style: GoogleFonts.poppins(),
),
],
),
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12),
),
child: DropdownButtonHideUnderline(
child: DropdownButtonFormField<String>(
value: _selectedGender,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.person_outline,
color: Colors.blue),
border: InputBorder.none,
labelStyle: GoogleFonts.poppins(),
),
items: ['Male', 'Female', 'Other']
.map((gender) => DropdownMenuItem(
value: gender,
child: Text(gender,
style: GoogleFonts.poppins()),
))
.toList(),
onChanged: (value) {
if (value != null) {
setDialogState(() => _selectedGender = value);
}
},
),
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'Cancel',
style: GoogleFonts.poppins(color: Colors.grey),
),
),
ElevatedButton(
onPressed: () => _addFamilyMember(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Add Member',
style: GoogleFonts.poppins(color: Colors.white),
),
),
],
);
},
),
);
}
Future<void> _addFamilyMember(BuildContext context) async {
if (_nameController.text.isEmpty ||
_relationController.text.isEmpty ||
_selectedDateOfBirth == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Please fill in all fields',
style: GoogleFonts.poppins(),
),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
return;
}
try {
final newFamilyMember = FamilyMember(
name: _nameController.text,
relation: _relationController.text,
gender: _selectedGender,
dateOfBirth: _selectedDateOfBirth,
);
if (selectedPatient != null) {
selectedPatient!.familyMembers.add(newFamilyMember);
await PatientProfileService.updatePatientProfile(selectedPatient!);
setState(() {
selectedFamilyMember = newFamilyMember;
});
}
if (context.mounted) {
Navigator.pop(context);
_showPatientSelectionDialog();
}
} catch (e) {
if (context.mounted) {
Navigator.pop(context); // Pop add family member dialog
}
}
}
void _showPatientSelectionDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Text(
'Select Patient',
style: GoogleFonts.poppins(fontWeight: FontWeight.w600),
),
content: SizedBox(
width: double.maxFinite,
child: ListView(
shrinkWrap: true,
children: [
// Main patient
_buildPatientTile(
name: selectedPatient?.name ?? '',
subtitle: 'Primary Patient',
isSelected: selectedFamilyMember == null,
onTap: () {
setState(() {
selectedFamilyMember = null;
});
Navigator.pop(context);
},
),
const Divider(),
// Family members
...selectedPatient?.familyMembers.map(
(member) => _buildPatientTile(
name: member.name ?? '',
subtitle: member.relation ?? '',
isSelected: selectedFamilyMember == member,
onTap: () {
setState(() {
selectedFamilyMember = member;
});
Navigator.pop(context);
},
),
) ??
[],
const Divider(),
ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.person_add, color: Colors.blue),
),
title: Text(
'Add Family Member',
style: GoogleFonts.poppins(
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
onTap: () {
Navigator.pop(context);
_showAddFamilyMemberDialog();
},
),
],
),
),
),
);
}
Widget _buildPatientTile({
required String name,
required String subtitle,
required bool isSelected,
required VoidCallback onTap,
}) {
return ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelected
? Colors.blue.withOpacity(0.1)
: Colors.grey.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.person,
color: isSelected ? Colors.blue : Colors.grey,
),
),
title: Text(
name,
style: GoogleFonts.poppins(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected ? Colors.blue : Colors.black87,
),
),
subtitle: Text(
subtitle,
style: GoogleFonts.poppins(
color: Colors.grey[600],
),
),
trailing: isSelected
? const Icon(Icons.check_circle, color: Colors.blue)
: null,
onTap: onTap,
);
}
}

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:telemednet/data/models/consultation_center.dart';
import 'package:telemednet/data/models/doctor.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:intl/intl.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/route/route_names.dart';
class ConsultationTimeScreen extends StatefulWidget {
final Doctor doctor;
@ -134,7 +134,7 @@ class _ConsultationTimeScreenState extends State<ConsultationTimeScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.doctor.lastName!,
widget.doctor.firstName ?? '',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
@ -152,7 +152,7 @@ class _ConsultationTimeScreenState extends State<ConsultationTimeScreen> {
),
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
child: Image.network(
widget.doctor.profileImage!,
width: 60,
height: 60,
@ -474,7 +474,7 @@ class _ConsultationTimeScreenState extends State<ConsultationTimeScreen> {
children: [
_buildConfirmationDetail(
'Doctor',
widget.doctor.lastName!,
widget.doctor.firstName!,
),
const SizedBox(height: 8),
_buildConfirmationDetail(

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:telemednet/data/models/doctor.dart';
import 'package:telemednet/data/models/consultation_center.dart';
import 'package:telemednet/data/services/consultation_center_service.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:medora/data/models/consultation_center.dart';
import 'package:medora/data/services/consultation_center_service.dart';
import 'package:medora/route/route_names.dart';
class ConsultationsCenterScreen extends StatefulWidget {
final Doctor doctor;
@ -143,7 +143,7 @@ class _ConsultationsCenterScreenState extends State<ConsultationsCenterScreen> {
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
child: Image.network(
widget.doctor.profileImage!,
width: 80,
height: 80,
@ -164,7 +164,7 @@ class _ConsultationsCenterScreenState extends State<ConsultationsCenterScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.doctor.lastName!,
widget.doctor.firstName ?? "",
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:telemednet/data/models/doctor.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:medora/route/route_names.dart';
class DoctorDetailsScreen extends StatelessWidget {
final Doctor doctor;
@ -115,7 +115,7 @@ class DoctorDetailsScreen extends StatelessWidget {
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
child: Image.network(
doctor.profileImage!,
width: 100,
height: 100,
@ -137,7 +137,7 @@ class DoctorDetailsScreen extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
doctor.lastName!,
doctor.firstName ?? '',
style: GoogleFonts.poppins(
fontSize: 20,
fontWeight: FontWeight.w600,

View File

@ -2,9 +2,9 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:telemednet/data/models/doctor.dart';
import 'package:medora/data/models/doctor.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/route/route_names.dart';
class DoctorsListScreen extends StatefulWidget {
final String specialty;
@ -174,7 +174,7 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
.where((doctor) {
if (_searchController.text.isEmpty) return true;
final searchQuery = _searchController.text.toLowerCase();
return doctor.surName!.toLowerCase().contains(searchQuery) ||
return doctor.firstName!.toLowerCase().contains(searchQuery) ||
doctor.city!.toLowerCase().contains(searchQuery);
}).toList();
@ -244,7 +244,7 @@ class _DoctorsListScreenState extends State<DoctorsListScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
doctor.surName!,
doctor.firstName ?? '',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:animate_do/animate_do.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/route/route_names.dart';
class Specialty {
final String name;

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:telemednet/screens/patientScreens/patientDashboard/patient_home_screen.dart';
import 'package:telemednet/screens/patientScreens/patientDashboard/patient_profile_screen.dart';
import 'package:medora/screens/patientScreens/patientDashboard/patient_home_screen.dart';
import 'package:medora/screens/patientScreens/patientDashboard/patient_profile_screen.dart';
class PatientDashboardScreen extends StatefulWidget {
const PatientDashboardScreen({super.key});
@ -46,8 +46,8 @@ class _PatientDashboardScreenState extends State<PatientDashboardScreen> {
index: _selectedIndex,
items: const [
Icon(Icons.home, size: 30, color: Colors.white),
// Icon(Icons.chat_bubble, size: 30, color: Colors.white),
Icon(Icons.list, size: 30, color: Colors.white),
Icon(Icons.chat_bubble, size: 30, color: Colors.white),
Icon(Icons.assignment, size: 30, color: Colors.white),
Icon(Icons.person, size: 30, color: Colors.white),
],
onTap: (index) {

View File

@ -1,8 +1,13 @@
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:telemednet/screens/patientScreens/appoinmentBooking/speciality_screen.dart';
import 'package:medora/screens/patientScreens/appoinmentBooking/speciality_screen.dart';
class PatientHomeScreen extends StatefulWidget {
const PatientHomeScreen({super.key});
@ -14,6 +19,8 @@ class PatientHomeScreen extends StatefulWidget {
class _PatientHomeScreenState extends State<PatientHomeScreen>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
final BookingService _bookingService = BookingService();
late Stream<List<Booking>> _bookingsStream;
@override
void initState() {
@ -23,6 +30,14 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
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
@ -50,7 +65,6 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
],
),
),
// _buildBottomNavBar(),
],
),
),
@ -61,14 +75,7 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.blue[400]!,
Colors.blue[300]!,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
color: Colors.blue,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(30.0),
bottomRight: Radius.circular(30.0),
@ -179,47 +186,161 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Upcoming Consultations',
style: GoogleFonts.poppins(
fontSize: 20,
fontWeight: FontWeight.bold,
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: 16),
const SizedBox(height: 20),
SizedBox(
height: 160,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
_consultationCard(
'Dr. Smith', '23/09/2024\n5:00 PM - 6:00 PM', 'Cardiologist'),
const SizedBox(width: 16),
_consultationCard('Dr. Johnson', '24/09/2024\n3:30 PM - 4:30 PM',
'Pediatrician'),
],
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) {
Widget _consultationCard(
String name,
String schedule,
String speciality,
PaymentStatus paymentStatus,
) {
return Container(
width: 280,
padding: const EdgeInsets.all(16),
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(20),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 5),
color: Colors.grey.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
@ -229,19 +350,30 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
Row(
children: [
Container(
width: 60,
height: 60,
width: 64,
height: 64,
decoration: BoxDecoration(
color: Colors.blue[100],
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: 40,
size: 36,
color: Colors.white,
),
),
const SizedBox(width: 12),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -249,14 +381,50 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
Text(
name,
style: GoogleFonts.poppins(
fontSize: 16,
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,
),
),
],
),
),
],
@ -264,26 +432,79 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
),
],
),
const SizedBox(height: 12),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(10),
),
child: Text(
schedule,
style: GoogleFonts.poppins(
color: Colors.blue[700],
fontSize: 12,
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 = [
{
@ -396,7 +617,13 @@ class _PatientHomeScreenState extends State<PatientHomeScreen>
}) {
return GestureDetector(
onTap: () {
// Replace with your desired navigation action
Navigator.pushNamed(
context,
RouteNames.doctorListScreen,
arguments: {
'specialty': label,
},
);
},
child: Container(
width: 140,

View File

@ -1,6 +1,8 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/route_names.dart';
import 'package:medora/data/services/patient_registration_service.dart';
import 'package:medora/route/route_names.dart';
import 'package:medora/data/models/patient.dart';
class PatientProfileScreen extends StatefulWidget {
const PatientProfileScreen({super.key});
@ -11,7 +13,23 @@ class PatientProfileScreen extends StatefulWidget {
class _PatientProfileScreenState extends State<PatientProfileScreen> {
final FirebaseAuth _auth = FirebaseAuth.instance;
// final GlobalKey<CurvedNavigationBarState> _bottomNavigationKey = GlobalKey();
PatientModel? _patientProfile;
@override
void initState() {
super.initState();
_fetchPatientProfile();
}
Future<void> _fetchPatientProfile() async {
final patientProfile = await PatientProfileService.getPatientProfile();
if (mounted) {
setState(() {
_patientProfile = patientProfile;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -20,8 +38,6 @@ class _PatientProfileScreenState extends State<PatientProfileScreen> {
children: [
_buildProfileHeader(),
_buildProfileOptions(),
const Spacer(),
// _buildBottomNavBar(),
],
),
),
@ -50,41 +66,46 @@ class _PatientProfileScreenState extends State<PatientProfileScreen> {
Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
image: _patientProfile?.profileImageUrl != null
? DecorationImage(
image: NetworkImage(_patientProfile!.profileImageUrl!),
fit: BoxFit.cover,
)
: null,
),
child: const Center(
child: Text(
'D',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
child: _patientProfile?.profileImageUrl == null
? Center(
child: Text(
_patientProfile != null && _patientProfile!.name != null
? _patientProfile!.name![0].toUpperCase()
: '',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
)
: null,
),
const SizedBox(width: 16),
const Expanded(
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Dhansh A S',
style: TextStyle(
_patientProfile != null && _patientProfile!.name != null
? _patientProfile!.name!
: 'Create your profile',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
'User profile is incomplete',
style: TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
],
),
),
@ -117,7 +138,9 @@ class _PatientProfileScreenState extends State<PatientProfileScreen> {
_buildOptionTile(
'Medical Profile',
Icons.medical_information_outlined,
onTap: () {},
onTap: () {
// Add navigation or action
},
),
const Divider(height: 1),
_buildOptionTile(

View File

@ -1,5 +1,5 @@
import 'package:medora/route/route_names.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/route_names.dart';
class PatientLandingScreen extends StatelessWidget {
const PatientLandingScreen({super.key});

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:telemednet/controllers/patient_controller.dart';
import 'package:telemednet/data/models/patient.dart';
import 'package:medora/controllers/patient_controller.dart';
import 'package:medora/data/models/patient.dart';
class FamilyMembersEditScreen extends StatefulWidget {
final FamilyMember? familyMember;

View File

@ -1,6 +1,6 @@
import 'package:medora/controllers/patient_controller.dart';
import 'package:flutter/material.dart';
import 'package:country_state_city_picker/country_state_city_picker.dart';
import 'package:telemednet/controllers/patient_controller.dart';
class PatientAddressScreen extends StatefulWidget {
final PatientController? controller;

View File

@ -1,6 +1,6 @@
import 'package:medora/data/models/patient.dart';
import 'package:medora/screens/patientScreens/registrationScreens/family_members_edit_screen.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/screens/patientScreens/registrationScreens/family_members_edit_screen.dart';
import 'package:telemednet/data/models/patient.dart';
import '../../../controllers/patient_controller.dart';
import 'package:flutter_slidable/flutter_slidable.dart';

View File

@ -1,6 +1,6 @@
import 'package:medora/route/route_names.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:telemednet/route_names.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';

View File

@ -0,0 +1,38 @@
import 'package:medora/data/services/navigation_service.dart';
import 'package:flutter/material.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 3), () {
if (!mounted) return;
_navigateToAppropriateScreen();
});
}
Future<void> _navigateToAppropriateScreen() async {
await NavigationService.handleUserNavigation(context);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('images/splash_screen.jpg'),
fit: BoxFit.cover,
),
),
),
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:medora/route/route_names.dart';
import 'package:medora/route/routes.dart';
import 'package:medora/screens/splash_screen.dart';
import 'package:flutter/material.dart';
import 'package:telemednet/route_names.dart';
import 'package:telemednet/routes.dart';
class TelemednetApp extends StatefulWidget {
const TelemednetApp({super.key});
@ -15,14 +16,15 @@ class _TelemednetAppState extends State<TelemednetApp> {
super.initState();
}
Future<void> login() async {}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: RouteNames.launch,
routes: routes,
initialRoute: RouteNames.splashScreen,
routes: {
RouteNames.splashScreen: (context) => const SplashScreen(),
...routes,
},
);
}
}

View File

@ -9,6 +9,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.44"
animate_do:
dependency: "direct main"
description:
name: animate_do
sha256: "7a3162729f0ea042f9dd84da217c5bde5472ad9cef644079929d4304a5dc4ca0"
url: "https://pub.dev"
source: hosted
version: "3.3.4"
animations:
dependency: "direct main"
description:
name: animations
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
url: "https://pub.dev"
source: hosted
version: "2.0.11"
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
@ -33,6 +57,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
carousel_slider:
dependency: "direct main"
description:
name: carousel_slider
sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
characters:
dependency: transitive
description:
@ -41,6 +73,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
@ -121,6 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
curved_navigation_bar:
dependency: "direct main"
description:
name: curved_navigation_bar
sha256: bb4ab128fcb6f4a9f0f1f72d227db531818b20218984789777f049fcbf919279
url: "https://pub.dev"
source: hosted
version: "1.0.6"
desktop_webview_auth:
dependency: transitive
description:
@ -145,6 +201,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
fhir:
dependency: "direct main"
description:
@ -326,6 +390,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.2.1"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77"
url: "https://pub.dev"
source: hosted
version: "0.14.1"
flutter_lints:
dependency: "direct dev"
description:
@ -355,6 +427,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.1"
flutter_staggered_animations:
dependency: "direct main"
description:
name: flutter_staggered_animations
sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter_staggered_grid_view:
dependency: "direct main"
description:
name: flutter_staggered_grid_view
sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
flutter_svg:
dependency: transitive
description:
@ -389,6 +477,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.1"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
url: "https://pub.dev"
source: hosted
version: "6.2.1"
http:
dependency: transitive
description:
@ -405,6 +501,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
url: "https://pub.dev"
source: hosted
version: "4.3.0"
image_picker:
dependency: "direct main"
description:
@ -558,7 +662,7 @@ packages:
source: hosted
version: "2.0.0"
path:
dependency: transitive
dependency: "direct main"
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
@ -569,10 +673,58 @@ packages:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.0.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
url: "https://pub.dev"
source: hosted
version: "2.2.12"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
@ -581,6 +733,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@ -589,6 +749,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
shimmer:
dependency: "direct main"
description:
name: shimmer
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
@ -714,6 +882,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:

View File

@ -1,4 +1,4 @@
name: telemednet
name: medora
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
@ -57,6 +57,8 @@ dependencies:
flutter_staggered_animations: ^1.1.1
flutter_staggered_grid_view: ^0.7.0
carousel_slider: ^5.0.0
path: ^1.9.0
dev_dependencies:
flutter_test:
@ -68,13 +70,18 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0
flutter_launcher_icons: ^0.14.1
flutter_icons:
android: true
ios: true
image_path: "images/logo.jpg"
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
@ -90,6 +97,7 @@ flutter:
- images/patient-avathar.png
- images/MaleDr.jpg.png
- images/FemaleDr.jpg.png
- images/splash_screen.jpg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images