import 'package:expandable_text/expandable_text.dart'; import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import '../components/external_link_widget.dart'; import '../components/language_list.dart'; import '../constants.dart'; import '../enumerations.dart'; import '../forms/corporate_culture_form.dart'; import '../forms/risks_form.dart'; import '../forms/sectors_form.dart'; import '../forms/skills_form.dart'; import '../models/user_profile.dart'; import '../services/user_service.dart'; import '../utils/helper.dart'; import '../utils/list_utils.dart'; import '../utils/math.dart'; import 'edit_profile_page.dart'; import 'user_data_page.dart'; import 'user_vision_page.dart'; class UserProfilePage extends StatefulWidget { const UserProfilePage({super.key, this.userId}); final String? userId; @override State createState() => _UserProfilePageState(); } class _UserProfilePageState extends State { String? profileImageUrl; // Track the profile image URL late UserProfile myData; bool isLoading = true; late bool isOwner; late String _userId; @override void initState() { super.initState(); // Determine the userId to use, then check if user is the current user _userId = widget.userId ?? FirebaseAuth.instance.currentUser!.uid; isOwner = (_userId == FirebaseAuth.instance.currentUser!.uid); // Load user data on initialization _loadUserData(); } Future _loadUserData() async { myData = await UserService.getUserProfileById(_userId); setState(() { // Initialize the profile image URL profileImageUrl = myData.profilePictureUrl; // Set loading to false once data is loaded isLoading = false; }); } void editNameInfo() async { final updatedUserData = await Navigator.push( context, MaterialPageRoute( builder: (context) => EditProfilePage(userData: myData), ), ); if (updatedUserData != null) { setState(() { // above Type of updatedUserData is dynamic, so check EditProfilePage profileImageUrl = updatedUserData[Constants.dbFieldUsersProfilePic]; myData.profilePictureUrl = updatedUserData[Constants.dbFieldUsersProfilePic]; myData.name = updatedUserData[Constants.dbFieldUsersName]; myData.bio = updatedUserData[Constants.dbFieldUsersBio]; myData.urlFacebook = updatedUserData[Constants.dbFieldUsersUrlFacebook]; myData.urlLinkedIn = updatedUserData[Constants.dbFieldUsersUrlLinkedIn]; myData.urlXing = updatedUserData[Constants.dbFieldUsersUrlXing]; }); } } void editUserDataInfo() async { final updatedUserData = await Navigator.push( context, MaterialPageRoute( builder: (context) => const UserDataPage(isRegProcess: false, isEditMode: true), ), ); if (updatedUserData != null) { setState(() { // above Type of updatedUserData is dynamic, so check UserDataPage myData.born = updatedUserData[Constants.dbFieldUsersYearBorn]; myData.gender = updatedUserData[Constants.dbFieldUsersGender]; myData.languages = updatedUserData[Constants.dbCollectionLanguages]; myData.locations = updatedUserData[Constants.dbCollectionLocations]; }); } } void _editSkills({required bool skillsSought}) async { final updatedUserData = await Navigator.push( context, MaterialPageRoute( builder: (context) => SkillsForm( isRegProcess: false, skillsSought: skillsSought, isEditMode: true, ), ), ); if (updatedUserData != null) { setState(() { // above Type of updatedUserData is dynamic, so convert if (skillsSought) { List dynamicList = updatedUserData[Constants.dbFieldUsersSkillsSought]; myData.skillsSought = dynamicList.map((e) => e as SkillOption).toList(); } else { List dynamicList = updatedUserData[Constants.dbFieldUsersSkills]; myData.skills = dynamicList.map((e) => e as SkillOption).toList(); } }); } } void editUserSkillsInfo() async { _editSkills(skillsSought: false); } void editUserSkillsSoughtInfo() async { _editSkills(skillsSought: true); } void editUserSectorsInfo() async { final updatedUserData = await Navigator.push( context, MaterialPageRoute( builder: (context) => SectorsForm( isRegProcess: false, isEditMode: true, ), ), ); if (updatedUserData != null) { setState(() { // above Type of updatedUserData is dynamic, so convert List dynamicList = updatedUserData[Constants.dbFieldUsersSectors]; myData.sectors = dynamicList.map((e) => e as SectorOption).toList(); }); } } void editUserVisionInfo() async { final updatedUserData = await Navigator.push( context, MaterialPageRoute( builder: (context) => const UserVisionPage( isRegProcess: false, isEditMode: true, ), ), ); if (updatedUserData != null) { setState(() { // above Type of updatedUserData is dynamic, so convert List dynamicList = updatedUserData[Constants.dbFieldUsersVisions]; myData.visions = dynamicList.map((e) => e as VisionOption).toList(); myData.availability = updatedUserData[Constants.dbFieldUsersAvailability]; }); } } void editUserWorkCultureInfo() async { final updatedUserData = await Navigator.push( context, MaterialPageRoute( builder: (context) => const CultureValuesFormPage( isRegProcess: false, isEditMode: true, ), ), ); if (updatedUserData != null) { setState(() { // above Type of updatedUserData is dynamic, so convert List dynamicList = updatedUserData[Constants.dbFieldUsersWorkValues]; myData.workValues = dynamicList.map((e) => e as WorkValueOption).toList(); myData.culture = updatedUserData[Constants.dbFieldUsersCorpCulture]; }); } } void editUserRiskInfo() async { final updatedUserData = await Navigator.push( context, MaterialPageRoute( builder: (context) => const RisksFormPage( isRegProcess: false, isEditMode: true, ), ), ); if (updatedUserData != null) { setState(() { // above Type of updatedUserData is dynamic, so convert myData.risk = updatedUserData[Constants.dbFieldUsersRiskTolerance]; myData.communication = updatedUserData[Constants.dbFieldUsersCommunication]; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(isOwner ? 'My Profile Information' : 'Profile Information'), ), body: isLoading ? const Center(child: CircularProgressIndicator()) : Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( children: [ _buildAvatar(context), const SizedBox(height: 16), if (isOwner) Divider(color: Theme.of(context).colorScheme.primary), if (isOwner) const SizedBox(height: 16), _buildLocation(context), const SizedBox(height: 16), if (isOwner) Divider(color: Theme.of(context).colorScheme.primary), if (isOwner) const SizedBox(height: 16), _buildSkills(context), const SizedBox(height: 16), if (isOwner) Divider(color: Theme.of(context).colorScheme.primary), if (isOwner) const SizedBox(height: 16), _buildSectors(context), const SizedBox(height: 16), if (isOwner) Divider(color: Theme.of(context).colorScheme.primary), if (isOwner) const SizedBox(height: 16), _buildRisks(context), const SizedBox(height: 16), if (isOwner) Divider(color: Theme.of(context).colorScheme.primary), if (isOwner) const SizedBox(height: 16), _buildVision(context), const SizedBox(height: 16), if (isOwner) Divider(color: Theme.of(context).colorScheme.primary), if (isOwner) const SizedBox(height: 16), _buildWorkCulture(context), const SizedBox(height: 16), if (isOwner) Divider(color: Theme.of(context).colorScheme.primary), if (isOwner) const SizedBox(height: 16), ], ), ), ), ); } Widget _buildAvatar(BuildContext context) { Widget genderIcon = const Icon(null); if (myData.gender == Gender.male) { genderIcon = const Padding( padding: EdgeInsets.only(left: 4.0), child: Icon(Icons.male, color: Colors.blue), ); } else if (myData.gender == Gender.female) { genderIcon = const Icon(Icons.female, color: Colors.pink); } return Column( children: [ if (isOwner) _editButton(context, editNameInfo, alignment: Alignment.bottomRight), CircleAvatar( radius: 80, backgroundImage: (profileImageUrl != null && profileImageUrl!.isNotEmpty) ? NetworkImage(profileImageUrl!) : null, child: (profileImageUrl == null || profileImageUrl!.isEmpty) ? const Icon(Icons.person, size: 80) : null, ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Flexible( child: Text( isOwner ? myData.name : '${myData.name} ${ageInfo(myData.born)}'.trim(), style: const TextStyle(fontSize: 24), ), ), genderIcon, ], ), if (isOwner) Text(myData.email, style: const TextStyle(fontSize: 16)), if (myData.urlXing != null && myData.urlXing!.isNotEmpty) ExternalLinkWidget(url: myData.urlXing!, context: context), if (myData.urlLinkedIn != null && myData.urlLinkedIn!.isNotEmpty) ExternalLinkWidget(url: myData.urlLinkedIn!, context: context), if (myData.urlFacebook != null && myData.urlFacebook!.isNotEmpty) ExternalLinkWidget(url: myData.urlFacebook!, context: context), const SizedBox(height: 32), Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( isOwner ? 'Short description of yourself' : 'Short description', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), ExpandableText( linkEllipsis: false, collapseOnTextTap: true, expandOnTextTap: true, myData.bio ?? 'n/a', expandText: '[Expand]', collapseText: '[Collapse]', maxLines: 3, linkColor: Colors.blue, style: const TextStyle(fontSize: 16), ), ], ), ), ], ); } Widget _buildLocation(BuildContext context) { int age = calcAge(myData.born); return Column( children: [ Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( myData.locations.length > 1 ? 'Locations' : 'Location', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), if (myData.locations.isEmpty) const Text('n/a', style: TextStyle(fontSize: 16)), if (myData.locations .containsKey(Constants.dbDocMainLocation)) Text( myData.locations[Constants.dbDocMainLocation] .toString(), style: const TextStyle(fontSize: 16), ), if (myData.locations .containsKey(Constants.dbDocSecondLocation)) Text( myData.locations[Constants.dbDocSecondLocation] .toString(), style: const TextStyle(fontSize: 16), ), ], ), if (isOwner) _editButton(context, editUserDataInfo, alignment: Alignment.bottomRight), ], ), if (isOwner) const SizedBox(height: 16), if (isOwner) Text( 'Age', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), if (isOwner) Text( (age > 0 ? '$age years old, born ${myData.born}' : 'n/a'), style: const TextStyle(fontSize: 16), ), if (isOwner) const SizedBox(height: 16), if (isOwner) Text( 'Gender', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), if (isOwner) Text(getDisplayText(myData.gender), style: const TextStyle(fontSize: 16)), const SizedBox(height: 16), Text( 'Spoken languages', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), MyLanguageList( langList: myData.languages, textSize: 16, ), ], ), ), ], ); } Widget _buildSkills(BuildContext context) { return Column( children: [ Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Skills offered', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text(myData.skills.map((x) => x.displayName).join(', '), style: const TextStyle(fontSize: 16)), ], ), ), if (isOwner) _editButton(context, editUserSkillsInfo), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Skills sought', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text( myData.skillsSought .map((x) => x.displayName) .join(', '), style: const TextStyle(fontSize: 16)), ], ), ), if (isOwner) _editButton(context, editUserSkillsSoughtInfo), ], ), ], ), ), ], ); } Widget _buildSectors(BuildContext context) { return Column( children: [ Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sectors of Interest', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text( sortSectorsList(myData.sectors) .map((x) => x.displayName) .join('\n'), style: const TextStyle(fontSize: 16)), ], ), ), if (isOwner) _editButton(context, editUserSectorsInfo), ], ), ], ), ), ], ); } Widget _buildRisks(BuildContext context) { return Column( children: [ Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Availability', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text(myData.availability.displayName, style: const TextStyle(fontSize: 16)), ], ), ), if (isOwner) _editButton(context, editUserRiskInfo), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Risk profile', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text(myData.risk.displayName, style: const TextStyle(fontSize: 16)), ], ), ), ], ), ], ), ), ], ); } Widget _buildVision(BuildContext context) { return Column( children: [ Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Communication profile', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text(myData.communication.displayName, style: const TextStyle(fontSize: 16)), ], ), ), if (isOwner) Padding( padding: const EdgeInsets.only(left: 8.0), child: _editButton(context, editUserVisionInfo), ), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Vision and Goals', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text( myData.visions .map((x) => x.displayName) .join(',\n'), style: const TextStyle(fontSize: 16)), ], ), ), ], ), ], ), ), ], ); } Widget _buildWorkCulture(BuildContext context) { return Column( children: [ Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Corporate culture', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text(myData.culture.displayName, style: const TextStyle(fontSize: 16)), ], ), ), if (isOwner) Padding( padding: const EdgeInsets.only(left: 8.0), child: _editButton(context, editUserWorkCultureInfo), ), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Corporate values', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), ), Text( myData.workValues .map((x) => x.displayName) .join(',\n'), style: const TextStyle(fontSize: 16)), ], ), ), ], ), ], ), ), ], ); } Widget _editButton(BuildContext context, void Function()? onPressedFunction, {Alignment alignment = Alignment.topRight}) { return Align( alignment: alignment, child: IconButton( icon: const Icon(Icons.edit), onPressed: onPressedFunction, ), ); } }