diff --git a/images/patient-avathar.png b/images/patient-avathar.png new file mode 100644 index 0000000..68b21c6 Binary files /dev/null and b/images/patient-avathar.png differ diff --git a/lib/data/data_service.dart b/lib/data/data_service.dart index b1fa1e4..54b6bea 100644 --- a/lib/data/data_service.dart +++ b/lib/data/data_service.dart @@ -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/telemed_user.dart'; +import 'package:telemednet/data/models/telemed_user.dart'; class DataService { static final String profileCollectionName = diff --git a/lib/data/models/patient.dart b/lib/data/models/patient.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/data/models/patient.dart @@ -0,0 +1 @@ + diff --git a/lib/data/telemed_user.dart b/lib/data/models/telemed_user.dart similarity index 100% rename from lib/data/telemed_user.dart rename to lib/data/models/telemed_user.dart diff --git a/lib/route_names.dart b/lib/route_names.dart index 3d8ef74..9d65d47 100644 --- a/lib/route_names.dart +++ b/lib/route_names.dart @@ -3,4 +3,13 @@ class RouteNames { static const String userProfile = '/user-profile'; static const String userHome = '/user-home'; static const String signUp = '/sign-up'; + static const String launch = '/launch'; + static const String patientLandingScreen = '/patient-landing-screen'; + static const String patientDashboardScreen = '/patient-dahboard-screen'; + static const String patientRegistrationScreen = + '/patient-registration-screen'; + static const String patientAdressScreen = '/patient-adress-screen'; + static const String patientFamilyMembersScreen = + '/patient-family-members-screen'; + static const String familyMembersEditScreen = '/family-members-edit-screen'; } diff --git a/lib/routes.dart b/lib/routes.dart index ff2fa15..336c2e8 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,14 +1,21 @@ // routes.dart import 'package:firebase_ui_auth/firebase_ui_auth.dart'; import 'package:flutter/material.dart'; -import 'package:telemednet/data/telemed_user.dart'; +import 'package:telemednet/data/models/telemed_user.dart'; import 'package:telemednet/screens/launch_screen.dart'; import 'package:telemednet/route_names.dart'; +import 'package:telemednet/screens/patientDashboard/patient_dashboard_screen.dart'; +import 'package:telemednet/screens/patientDashboard/registrationScreens/patient_adress_screen.dart'; +import 'package:telemednet/screens/patientDashboard/registrationScreens/patient_family_members_screen.dart'; +import 'package:telemednet/screens/patientDashboard/registrationScreens/patient_registration_screen.dart'; import 'package:telemednet/screens/user_profile_screen.dart'; import 'package:telemednet/screens/user_screen.dart'; +import 'screens/patientDashboard/patient_landing_screen.dart'; +import 'screens/patientDashboard/registrationScreens/family_members_edit_screen.dart'; + final Map routes = { - '/': (context) => const LaunchScreen(), + RouteNames.launch: (context) => const LaunchScreen(), RouteNames.signIn: (context) => SignInScreen( providers: [EmailAuthProvider(), PhoneAuthProvider()], ), @@ -18,4 +25,14 @@ final Map routes = { return UserProfileScreen(user: user); }, RouteNames.userHome: (context) => const UserScreen(), + RouteNames.patientLandingScreen: (context) => const PatientLandingScreen(), + RouteNames.patientDashboardScreen: (context) => + const PatientDashboardScreen(), + RouteNames.patientRegistrationScreen: (context) => + const PatientRegistrationScreen(), + RouteNames.patientAdressScreen: (context) => const PatientAddressScreen(), + RouteNames.patientFamilyMembersScreen: (context) => + const PatientFamilyMembersScreen(), + RouteNames.familyMembersEditScreen: (context) => + const FamilyMembersEditScreen(), }; diff --git a/lib/screens/launch_screen.dart b/lib/screens/authentication/launch_screen.dart similarity index 100% rename from lib/screens/launch_screen.dart rename to lib/screens/authentication/launch_screen.dart index 424680d..75262d2 100644 --- a/lib/screens/launch_screen.dart +++ b/lib/screens/authentication/launch_screen.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:telemednet/data/data_service.dart'; -import 'package:telemednet/widgets/primary_button.dart'; import 'package:telemednet/route_names.dart'; +import 'package:telemednet/widgets/primary_button.dart'; class LaunchScreen extends StatefulWidget { const LaunchScreen({super.key}); diff --git a/lib/screens/authentication/user_profile_screen.dart b/lib/screens/authentication/user_profile_screen.dart new file mode 100644 index 0000000..f6a09d3 --- /dev/null +++ b/lib/screens/authentication/user_profile_screen.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:telemednet/data/models/telemed_user.dart'; +import 'package:telemednet/route_names.dart'; +import 'package:telemednet/shared/user_selection.dart'; + +class UserProfileScreen extends StatefulWidget { + final TelemedUser? user; + const UserProfileScreen({super.key, required this.user}); + + @override + State createState() => _UserProfileScreenState(); +} + +class _UserProfileScreenState extends State { + TelemedUser? user; + final FirebaseAuth _auth = FirebaseAuth.instance; + + @override + void initState() { + super.initState(); + user = widget.user; + } + + Future _signOut() async { + try { + await _auth.signOut(); + // Navigate to login screen or home screen after logout + Navigator.of(context).pushReplacementNamed(RouteNames.launch); + } catch (e) { + print("Error signing out: $e"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to log out. Please try again.')), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('User Profile'), + actions: [ + IconButton( + icon: Icon(Icons.exit_to_app), + onPressed: _signOut, + ), + ], + ), + body: Column( + children: [ + Expanded(child: UserSelection()), + ElevatedButton( + onPressed: _signOut, + child: Text('Log Out'), + ), + ], + ), + ); + } +} diff --git a/lib/screens/patientDashboard/patient_dashboard_screen.dart b/lib/screens/patientDashboard/patient_dashboard_screen.dart new file mode 100644 index 0000000..1995665 --- /dev/null +++ b/lib/screens/patientDashboard/patient_dashboard_screen.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; + +class PatientDashboardScreen extends StatefulWidget { + const PatientDashboardScreen({super.key}); + + @override + State createState() => _PatientDashboardScreenState(); +} + +class _PatientDashboardScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Column( + children: [ + _buildSearchBar(), + Expanded( + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildRealTimeCard(), + const SizedBox(height: 20), + _buildConsultationsSection(), + const SizedBox(height: 20), + _buildFindDoctorSection(), + ], + ), + ), + _buildBottomNavBar(), + ], + ), + ), + ); + } + + Widget _buildSearchBar() { + return Container( + padding: const EdgeInsets.all(16), + decoration: const BoxDecoration( + color: Color.fromRGBO(96, 181, 250, 1), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(50.0), + bottomRight: Radius.circular(50.0)), + ), + child: TextField( + decoration: InputDecoration( + hintText: 'Search Doctor/Hospital/Symtoms', + prefixIcon: const Icon(Icons.search), + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(30), + borderSide: BorderSide.none, + ), + ), + ), + ); + } + + Widget _buildRealTimeCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.lightBlue[100]!, Colors.lightBlue[50]!], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Real-time care\nat your fingertips.', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () {}, + child: const Text('Consultation >'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + ), + ), + ], + ), + ); + } + + Widget _buildConsultationsSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Consultations', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + const SizedBox(height: 10), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + _consultationCard('Dr Pom', '23/09/2024\n5:00AM-7:00AM'), + const SizedBox(width: 10), + _consultationCard('Dr I', '23/09/2024\n5:00AM-7:00AM'), + ], + ), + ), + ], + ); + } + + Widget _consultationCard(String name, String schedule) { + return GestureDetector( + onTap: () { + // Handle the tap event + print('Tapped on consultation card for $name'); + // You can add more functionality here, like navigating to a detail page + }, + child: Card( + shadowColor: Colors.grey, + child: Container( + width: 200, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow(color: Colors.grey.withOpacity(0.5), blurRadius: 5), + ], + ), + child: Row( + children: [ + CircleAvatar( + radius: 30, + backgroundColor: Colors.blue[100], + child: const Icon(Icons.person, size: 40, color: Colors.white), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(name, + style: const TextStyle(fontWeight: FontWeight.bold)), + Text(schedule, style: const TextStyle(fontSize: 12)), + ], + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildFindDoctorSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Find a Doctor for your\nHealth Problem', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _categoryIcon(Icons.accessibility_new, Colors.blue), + _categoryIcon(Icons.remove_red_eye, Colors.blue), + _categoryIcon(Icons.medical_services, Colors.blue), + _categoryIcon(Icons.health_and_safety, Colors.blue), + _categoryIcon(Icons.child_care, Colors.blue), + ], + ), + ], + ); + } + + Widget _categoryIcon(IconData icon, Color color) { + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, color: Colors.white, size: 30), + ); + } + + Widget _buildBottomNavBar() { + return BottomNavigationBar( + type: BottomNavigationBarType.fixed, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), + BottomNavigationBarItem(icon: Icon(Icons.chat_bubble), label: 'Chat'), + BottomNavigationBarItem(icon: Icon(Icons.assignment), label: 'Records'), + BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'), + ], + currentIndex: 0, + selectedItemColor: Colors.blue, + unselectedItemColor: Colors.grey, + onTap: (index) { + // Handle navigation + }, + ); + } +} diff --git a/lib/screens/patientDashboard/patient_landing_screen.dart b/lib/screens/patientDashboard/patient_landing_screen.dart new file mode 100644 index 0000000..4fffa26 --- /dev/null +++ b/lib/screens/patientDashboard/patient_landing_screen.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:telemednet/route_names.dart'; + +class PatientLandingScreen extends StatelessWidget { + const PatientLandingScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.teal.shade100, Colors.white], + ), + ), + child: SafeArea( + child: Column( + children: [ + Expanded( + child: Center( + child: Card( + margin: const EdgeInsets.symmetric(horizontal: 32), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Align( + alignment: Alignment.topRight, + child: TextButton( + onPressed: () { + Navigator.of(context).pushNamed( + RouteNames.patientDashboardScreen); + }, + child: Text( + 'Skip', + style: TextStyle( + color: Colors.teal.shade300, + fontSize: 16, + fontWeight: FontWeight.bold), + ), + ), + ), + Image.asset( + 'images/patient-avathar.png', + height: 200, + width: 200, + ), + const SizedBox(height: 24), + const Text( + 'Set your medical profile', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () { + Navigator.of(context).pushNamed( + RouteNames.patientRegistrationScreen); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + minimumSize: const Size(double.infinity, 50), + ), + child: const Text( + 'Continue', + style: + TextStyle(fontSize: 18, color: Colors.white), + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/patientDashboard/registrationScreens/family_members_edit_screen.dart b/lib/screens/patientDashboard/registrationScreens/family_members_edit_screen.dart new file mode 100644 index 0000000..a2659eb --- /dev/null +++ b/lib/screens/patientDashboard/registrationScreens/family_members_edit_screen.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:telemednet/screens/patientDashboard/registrationScreens/patient_family_members_screen.dart'; + +class FamilyMembersEditScreen extends StatefulWidget { + final FamilyMember? familyMember; + + const FamilyMembersEditScreen({super.key, this.familyMember}); + + @override + State createState() => + _FamilyMembersEditScreenState(); +} + +class _FamilyMembersEditScreenState extends State { + late TextEditingController nameController; + late TextEditingController relationController; + late TextEditingController genderController; + late TextEditingController dobController; + + @override + void initState() { + super.initState(); + nameController = + TextEditingController(text: widget.familyMember?.name ?? ''); + relationController = + TextEditingController(text: widget.familyMember?.relation ?? ''); + genderController = + TextEditingController(text: widget.familyMember?.gender ?? ''); + dobController = + TextEditingController(text: widget.familyMember?.dateOfBirth ?? ''); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Family member'), + actions: [ + TextButton( + onPressed: () { + FamilyMember newMember = FamilyMember( + name: nameController.text, + relation: relationController.text, + gender: genderController.text, + dateOfBirth: dobController.text, + ); + Navigator.pop(context, newMember); + }, + child: const Text('Done', style: TextStyle(color: Colors.blue)), + ), + ], + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTextField(nameController, 'Name'), + _buildDropdownField('Relation', relationController.text, + (String? newValue) { + setState(() { + relationController.text = newValue ?? ''; + }); + }), + _buildDropdownField('Gender', genderController.text, + (String? newValue) { + setState(() { + genderController.text = newValue ?? ''; + }); + }), + _buildDateField(context), + const SizedBox(height: 16), + const Text('Address', style: TextStyle(fontSize: 16)), + const SizedBox(height: 8), + Card( + child: ListTile( + title: const Text('Enter address details'), + trailing: const Icon(Icons.chevron_right), + onTap: () { + // Navigate to address entry screen + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildTextField(TextEditingController controller, String label) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: TextField( + controller: controller, + decoration: InputDecoration( + labelText: label, + border: const OutlineInputBorder(), + ), + ), + ); + } + + Widget _buildDropdownField( + String label, String value, Function(String?) onChanged) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: DropdownButtonFormField( + decoration: InputDecoration( + labelText: label, + border: const OutlineInputBorder(), + ), + value: value.isEmpty ? null : value, + onChanged: onChanged, + items: ['Father', 'Mother', 'Son', 'Daughter'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ); + } + + Widget _buildDateField(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: TextField( + controller: dobController, + decoration: const InputDecoration( + labelText: 'Date of Birth', + border: OutlineInputBorder(), + suffixIcon: Icon(Icons.calendar_today), + ), + readOnly: true, + onTap: () async { + DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (pickedDate != null) { + setState(() { + dobController.text = + "${pickedDate.day}/${pickedDate.month}/${pickedDate.year}"; + }); + } + }, + ), + ); + } + + @override + void dispose() { + nameController.dispose(); + relationController.dispose(); + genderController.dispose(); + dobController.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/patientDashboard/registrationScreens/patient_adress_screen.dart b/lib/screens/patientDashboard/registrationScreens/patient_adress_screen.dart new file mode 100644 index 0000000..fe8855d --- /dev/null +++ b/lib/screens/patientDashboard/registrationScreens/patient_adress_screen.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:country_state_city_picker/country_state_city_picker.dart'; + +class PatientAddressScreen extends StatefulWidget { + const PatientAddressScreen({super.key}); + + @override + State createState() => _PatientAddressScreenState(); +} + +class _PatientAddressScreenState extends State { + final TextEditingController _houseNoController = TextEditingController(); + final TextEditingController _lineController = TextEditingController(); + final TextEditingController _townController = TextEditingController(); + final TextEditingController _pincodeController = TextEditingController(); + String? country; + String? state; + String? city; + String? addressType; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Address'), + actions: [ + TextButton( + onPressed: () { + // Save address logic here + }, + child: const Text('Done', style: TextStyle(color: Colors.blue)), + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTextField('House No', _houseNoController), + _buildTextField('Line', _lineController), + _buildTextField('Town', _townController, hintText: '(OPTIONAL)'), + _buildTextField('Pincode', _pincodeController), + const SizedBox(height: 20), + SelectState( + onCountryChanged: (value) { + setState(() { + country = value; + }); + }, + onStateChanged: (value) { + setState(() { + state = value; + }); + }, + onCityChanged: (value) { + setState(() { + city = value; + }); + }, + ), + const SizedBox(height: 20), + const Text('Type of address', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + _buildAddressTypeChips(), + if (addressType == 'Other') + const TextField( + decoration: InputDecoration( + hintText: 'Other Label...', + border: OutlineInputBorder(), + ), + ), + ], + ), + ), + ); + } + + Widget _buildTextField(String label, TextEditingController controller, + {String? hintText}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + TextField( + controller: controller, + decoration: InputDecoration( + hintText: hintText, + border: const UnderlineInputBorder(), + ), + ), + const SizedBox(height: 20), + ], + ); + } + + Widget _buildAddressTypeChips() { + return Wrap( + spacing: 8.0, + children: ['Home', 'Office', 'Other'].map((String type) { + return ChoiceChip( + label: Text(type), + selected: addressType == type, + onSelected: (bool selected) { + setState(() { + addressType = selected ? type : addressType; + }); + }, + ); + }).toList(), + ); + } +} diff --git a/lib/screens/patientDashboard/registrationScreens/patient_family_members_screen.dart b/lib/screens/patientDashboard/registrationScreens/patient_family_members_screen.dart new file mode 100644 index 0000000..d8117eb --- /dev/null +++ b/lib/screens/patientDashboard/registrationScreens/patient_family_members_screen.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:telemednet/screens/patientDashboard/registrationScreens/family_members_edit_screen.dart'; + +class FamilyMember { + final String name; + final String gender; + final String dateOfBirth; + final String relation; + + FamilyMember({ + required this.name, + required this.gender, + required this.dateOfBirth, + required this.relation, + }); +} + +class PatientFamilyMembersScreen extends StatefulWidget { + const PatientFamilyMembersScreen({super.key}); + + @override + State createState() => + _PatientFamilyMembersScreenState(); +} + +class _PatientFamilyMembersScreenState + extends State { + List familyMembers = [ + FamilyMember( + name: "Dhansh A S", + gender: "Male", + dateOfBirth: "18/12/2001", + relation: "Father", + ), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Family members'), + actions: [ + TextButton( + onPressed: () { + // Handle 'Done' action + }, + child: Text('Done', style: TextStyle(color: Colors.blue)), + ), + ], + ), + body: ListView.builder( + itemCount: familyMembers.length, + itemBuilder: (context, index) { + return FamilyMemberCard( + familyMember: familyMembers[index], + onEdit: () => _editFamilyMember(index), + onDelete: () => _deleteFamilyMember(index), + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: _addFamilyMember, + child: Icon(Icons.add), + backgroundColor: Colors.blue, + ), + ); + } + + void _addFamilyMember() { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const FamilyMembersEditScreen()), + ).then((newMember) { + if (newMember != null) { + setState(() { + familyMembers.add(newMember); + }); + } + }); + } + + void _editFamilyMember(int index) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + FamilyMembersEditScreen(familyMember: familyMembers[index]), + ), + ).then((editedMember) { + if (editedMember != null) { + setState(() { + familyMembers[index] = editedMember; + }); + } + }); + } + + void _deleteFamilyMember(int index) { + setState(() { + familyMembers.removeAt(index); + }); + } +} + +class FamilyMemberCard extends StatelessWidget { + final FamilyMember familyMember; + final VoidCallback onEdit; + final VoidCallback onDelete; + + const FamilyMemberCard({ + super.key, + required this.familyMember, + required this.onEdit, + required this.onDelete, + }); + + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Name: ${familyMember.name}', + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 4), + Text('Gender: ${familyMember.gender}'), + SizedBox(height: 4), + Text('Date of Birth: ${familyMember.dateOfBirth}'), + SizedBox(height: 4), + Text('Relation: ${familyMember.relation}'), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: Icon(Icons.edit, color: Colors.blue), + onPressed: onEdit, + ), + IconButton( + icon: Icon(Icons.delete, color: Colors.red), + onPressed: onDelete, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/patientDashboard/registrationScreens/patient_registration_screen.dart b/lib/screens/patientDashboard/registrationScreens/patient_registration_screen.dart new file mode 100644 index 0000000..09a1d3e --- /dev/null +++ b/lib/screens/patientDashboard/registrationScreens/patient_registration_screen.dart @@ -0,0 +1,226 @@ +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'; + +class PatientRegistrationScreen extends StatefulWidget { + const PatientRegistrationScreen({super.key}); + + @override + State createState() => + _PatientRegistrationScreenState(); +} + +class _PatientRegistrationScreenState extends State { + final TextEditingController _nameController = TextEditingController(); + final TextEditingController _phoneController = TextEditingController(); + String? _gender; + DateTime? _dateOfBirth; + File? _image; + final ImagePicker _picker = ImagePicker(); + + Future _getImage(ImageSource source) async { + final XFile? pickedFile = await _picker.pickImage(source: source); + + if (pickedFile != null) { + setState(() { + _image = File(pickedFile.path); + }); + } + } + + void _showImageSourceActionSheet(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SafeArea( + child: Wrap( + children: [ + ListTile( + leading: const Icon(Icons.photo_library), + title: const Text('Choose from Gallery'), + onTap: () { + _getImage(ImageSource.gallery); + Navigator.pop(context); + }, + ), + ListTile( + leading: const Icon(Icons.photo_camera), + title: const Text('Take a Photo'), + onTap: () { + _getImage(ImageSource.camera); + Navigator.pop(context); + }, + ), + ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Profile'), + actions: [ + IconButton( + icon: const Icon(Icons.check, color: Colors.blue), + onPressed: () { + // Save profile logic here + }, + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Column( + children: [ + GestureDetector( + onTap: () => _showImageSourceActionSheet(context), + child: CircleAvatar( + radius: 50, + backgroundImage: + _image != null ? FileImage(_image!) : null, + child: _image == null + ? const Icon(Icons.person, size: 50) + : null, + ), + ), + TextButton( + onPressed: () => _showImageSourceActionSheet(context), + child: const Text('Upload picture', + style: TextStyle(color: Colors.blue)), + ), + ], + ), + ), + const SizedBox(height: 20), + _buildTextField('Name', _nameController), + _buildTextField('Phone number', _phoneController), + _buildDropdownField('Gender', _gender, ['Male', 'Female', 'Other'], + (value) { + setState(() => _gender = value); + }), + _buildDateField('Date of Birth', _dateOfBirth), + _buildNavigationField('Address'), + _buildNavigationField('Family members'), + ], + ), + ), + ); + } + + Widget _buildTextField(String label, TextEditingController controller) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + TextField( + controller: controller, + decoration: const InputDecoration( + border: UnderlineInputBorder(), + ), + ), + const SizedBox(height: 20), + ], + ); + } + + Widget _buildDropdownField(String label, String? value, List items, + Function(String?) onChanged) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + DropdownButton( + value: value, + isExpanded: true, + hint: Text('Select $label'), + onChanged: onChanged, + items: items.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(height: 20), + ], + ); + } + + Widget _buildDateField(String label, DateTime? date) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(date != null + ? DateFormat('dd/MM/yyyy').format(date) + : 'Select date'), + IconButton( + icon: const Icon(Icons.calendar_today, color: Colors.blue), + onPressed: () async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: date ?? DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (picked != null && picked != date) { + setState(() { + _dateOfBirth = picked; + }); + } + }, + ), + ], + ), + const Divider(), + const SizedBox(height: 20), + ], + ); + } + + Widget _buildNavigationField(String label) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, + style: + const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + IconButton( + icon: const Icon(Icons.chevron_right, color: Colors.blue), + onPressed: () { + if (label == 'Address') { + Navigator.of(context) + .pushNamed(RouteNames.patientAdressScreen); + } else { + Navigator.of(context) + .pushNamed(RouteNames.patientFamilyMembersScreen); + } + }, + ), + ], + ), + const Divider(), + const SizedBox(height: 20), + ], + ); + } +} diff --git a/lib/screens/user_profile_screen.dart b/lib/screens/user_profile_screen.dart deleted file mode 100644 index cec377e..0000000 --- a/lib/screens/user_profile_screen.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:telemednet/data/telemed_user.dart'; - -class UserProfileScreen extends StatefulWidget { - final TelemedUser? user; - const UserProfileScreen({super.key, required this.user}); - - @override - State createState() => _UserProfileScreenState(); -} - -class _UserProfileScreenState extends State { - TelemedUser? user; - - @override - void initState() { - super.initState(); - user = widget.user; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('User Profile Screen'), - ), - body: const Center( - child: Text('User Profile Screen'), - ), - ); - } -} diff --git a/lib/shared/user_selection.dart b/lib/shared/user_selection.dart new file mode 100644 index 0000000..38cf0d5 --- /dev/null +++ b/lib/shared/user_selection.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; + +import '../route_names.dart'; + +class UserSelection extends StatefulWidget { + const UserSelection({super.key}); + + @override + State createState() => _UserSelectionState(); +} + +class _UserSelectionState extends State { + String _selectedUserType = ''; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Text( + 'Register', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + const Text( + 'Select User Type', + style: TextStyle(fontSize: 16), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + _buildSelectionOption( + icon: Icons.medical_services, + label: 'Doctor', + description: 'Can organise and approve appointments', + onTap: () => _selectUserType('Doctor'), + ), + const SizedBox(height: 12), + _buildSelectionOption( + icon: Icons.person, + label: 'Patient', + description: 'Can book appointments', + onTap: () => _selectUserType('Patient'), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () { + if (_selectedUserType == 'Patient') { + Navigator.of(context) + .pushNamed(RouteNames.patientLandingScreen); + } else {} + }, + child: const Text('Next'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildSelectionOption({ + required IconData icon, + required String label, + required String description, + required VoidCallback onTap, + }) { + final isSelected = _selectedUserType == label; + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all( + color: isSelected ? Colors.blue : Colors.grey[300]!, + width: 2, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon(icon, size: 40, color: Colors.blue), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 16, fontWeight: FontWeight.bold), + ), + Text( + description, + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + ], + ), + ), + if (isSelected) const Icon(Icons.check_circle, color: Colors.blue), + ], + ), + ), + ); + } + + void _selectUserType(String userType) { + setState(() { + _selectedUserType = userType; + }); + } +} diff --git a/lib/telemednet_app.dart b/lib/telemednet_app.dart index 573233a..c22d28c 100644 --- a/lib/telemednet_app.dart +++ b/lib/telemednet_app.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:telemednet/route_names.dart'; import 'package:telemednet/routes.dart'; class TelemednetApp extends StatefulWidget { @@ -19,7 +20,8 @@ class _TelemednetAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - initialRoute: '/', + debugShowCheckedModeBanner: false, + initialRoute: RouteNames.launch, routes: routes, ); } diff --git a/pubspec.lock b/pubspec.lock index b1700f1..85cab23 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,6 +81,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + country_state_city_picker: + dependency: "direct main" + description: + name: country_state_city_picker + sha256: "509c282165df329ee5a29babb28e13dce3bc32166cea21b202a49c44d20cfef3" + url: "https://pub.dev" + source: hosted + version: "1.2.8" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -121,6 +137,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + fhir: + dependency: "direct main" + description: + name: fhir + sha256: "5242574636c300849227902717e2b37e4df7791ea75bda6c47c2600de417546a" + url: "https://pub.dev" + source: hosted + version: "0.12.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" + url: "https://pub.dev" + source: hosted + version: "0.9.3" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" firebase_auth: dependency: "direct main" description: @@ -185,6 +241,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.6+44" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + sha256: e00e2909e36f5e44f839fd77dff90ff764f7bb303ed548d43617014ce05c77c8 + url: "https://pub.dev" + source: hosted + version: "12.3.3" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: "462621bbdb5ab496518aa0f4785cb6db87763d5f1063aa228e1f65562937af1d" + url: "https://pub.dev" + source: hosted + version: "5.1.31" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: d9221c943c1341ee2cba51857ddb5916686994b16b181e9e9d2e0d5a9056f9b7 + url: "https://pub.dev" + source: hosted + version: "3.10.3" firebase_ui_auth: dependency: "direct main" description: @@ -217,6 +297,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -243,6 +331,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + url: "https://pub.dev" + source: hosted + version: "2.0.23" flutter_svg: dependency: transitive description: @@ -261,6 +357,14 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" http: dependency: transitive description: @@ -277,14 +381,86 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - intl: + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: dependency: transitive + description: + name: image_picker_android + sha256: d34e0d9e024e81321b2aeed7b202ec6181cc282e6a1c0c0b4e6ad07ef1065d82 + url: "https://pub.dev" + source: hosted + version: "0.8.12+16" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" + url: "https://pub.dev" + source: hosted + version: "0.8.12+1" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + intl: + dependency: "direct main" description: name: intl sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted version: "0.19.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -341,6 +517,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" path: dependency: transitive description: @@ -386,6 +570,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -434,6 +626,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_graphics: dependency: transitive description: @@ -490,6 +690,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + xml2json: + dependency: transitive + description: + name: xml2json + sha256: "9523203b99032ce419672804010cce72ea47fc277b3135f77bbb7ac8fa391664" + url: "https://pub.dev" + source: hosted + version: "6.2.5" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.5.3 <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index bdb3bfe..3ae3820 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,11 @@ dependencies: firebase_auth: ^5.3.1 firebase_ui_auth: ^1.16.0 cloud_firestore: ^5.4.4 + firebase_storage: ^12.3.3 + image_picker: ^1.1.2 + fhir: ^0.12.0 + intl: ^0.19.0 + country_state_city_picker: ^1.2.8 dev_dependencies: flutter_test: @@ -70,6 +75,7 @@ flutter: assets: - .env - images/cover-picture.jpg + - images/patient-avathar.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images