UI complete (#2)

UI complete for patient registration

Co-authored-by: Benoy Bose <benoybose@gmail.com>
Reviewed-on: cosqnet/telemednet#2
Reviewed-by: Benoy Bose <benoybose@cosq.net>
Co-authored-by: DhanshCOSQ <dhanshas@cosq.net>
Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
This commit is contained in:
Dhansh A S 2024-10-31 06:04:36 +00:00 committed by Benoy Bose
parent 240ab136fc
commit ec433190c4
19 changed files with 1395 additions and 39 deletions

BIN
images/patient-avathar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

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/telemed_user.dart';
import 'package:telemednet/data/models/telemed_user.dart';
class DataService {
static final String profileCollectionName =

View File

@ -0,0 +1 @@

View File

@ -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';
}

View File

@ -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<String, Widget Function(BuildContext)> routes = {
'/': (context) => const LaunchScreen(),
RouteNames.launch: (context) => const LaunchScreen(),
RouteNames.signIn: (context) => SignInScreen(
providers: [EmailAuthProvider(), PhoneAuthProvider()],
),
@ -18,4 +25,14 @@ final Map<String, Widget Function(BuildContext)> 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(),
};

View File

@ -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});

View File

@ -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<UserProfileScreen> createState() => _UserProfileScreenState();
}
class _UserProfileScreenState extends State<UserProfileScreen> {
TelemedUser? user;
final FirebaseAuth _auth = FirebaseAuth.instance;
@override
void initState() {
super.initState();
user = widget.user;
}
Future<void> _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'),
),
],
),
);
}
}

View File

@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
class PatientDashboardScreen extends StatefulWidget {
const PatientDashboardScreen({super.key});
@override
State<PatientDashboardScreen> createState() => _PatientDashboardScreenState();
}
class _PatientDashboardScreenState extends State<PatientDashboardScreen> {
@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
},
);
}
}

View File

@ -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),
),
),
],
),
),
),
),
),
],
),
),
),
);
}
}

View File

@ -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<FamilyMembersEditScreen> createState() =>
_FamilyMembersEditScreenState();
}
class _FamilyMembersEditScreenState extends State<FamilyMembersEditScreen> {
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<String>(
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
),
value: value.isEmpty ? null : value,
onChanged: onChanged,
items: <String>['Father', 'Mother', 'Son', 'Daughter']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
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();
}
}

View File

@ -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<PatientAddressScreen> createState() => _PatientAddressScreenState();
}
class _PatientAddressScreenState extends State<PatientAddressScreen> {
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(),
);
}
}

View File

@ -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<PatientFamilyMembersScreen> createState() =>
_PatientFamilyMembersScreenState();
}
class _PatientFamilyMembersScreenState
extends State<PatientFamilyMembersScreen> {
List<FamilyMember> 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,
),
],
),
],
),
),
);
}
}

View File

@ -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<PatientRegistrationScreen> createState() =>
_PatientRegistrationScreenState();
}
class _PatientRegistrationScreenState extends State<PatientRegistrationScreen> {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
String? _gender;
DateTime? _dateOfBirth;
File? _image;
final ImagePicker _picker = ImagePicker();
Future<void> _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: <Widget>[
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<String> items,
Function(String?) onChanged) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
DropdownButton<String>(
value: value,
isExpanded: true,
hint: Text('Select $label'),
onChanged: onChanged,
items: items.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
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),
],
);
}
}

View File

@ -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<UserProfileScreen> createState() => _UserProfileScreenState();
}
class _UserProfileScreenState extends State<UserProfileScreen> {
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'),
),
);
}
}

View File

@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import '../route_names.dart';
class UserSelection extends StatefulWidget {
const UserSelection({super.key});
@override
State<UserSelection> createState() => _UserSelectionState();
}
class _UserSelectionState extends State<UserSelection> {
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;
});
}
}

View File

@ -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<TelemednetApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
debugShowCheckedModeBanner: false,
initialRoute: RouteNames.launch,
routes: routes,
);
}

View File

@ -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"

View File

@ -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