734 lines
24 KiB
Dart
734 lines
24 KiB
Dart
import 'package:expandable_text/expandable_text.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_auth/firebase_auth.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<UserProfilePage> createState() => _UserProfilePageState();
|
|
}
|
|
|
|
class _UserProfilePageState extends State<UserProfilePage> {
|
|
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<void> _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];
|
|
});
|
|
}
|
|
}
|
|
|
|
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<dynamic> dynamicList =
|
|
updatedUserData[Constants.dbFieldUsersSkillsSought];
|
|
myData.skillsSought =
|
|
dynamicList.map((e) => e as SkillOption).toList();
|
|
} else {
|
|
List<dynamic> 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<dynamic> 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<dynamic> 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<dynamic> 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)),
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
}
|