Edit Age, Gender, Locations, Languages.

master
Rafael 2024-05-31 03:20:42 +02:00
parent f4ed4bd110
commit d246829784
9 changed files with 272 additions and 93 deletions

View File

@ -136,6 +136,7 @@ class MyDrawer extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const UserDataPage( builder: (context) => const UserDataPage(
isRegProcess: false, isRegProcess: false,
isEditMode: false,
), ),
), ),
); );

View File

@ -2,7 +2,20 @@ enum Gender {
none, none,
male, male,
female, female,
divers, divers;
String get displayName {
switch (this) {
case Gender.none:
return 'unknown';
case Gender.male:
return 'male';
case Gender.female:
return 'female';
case Gender.divers:
return 'diverse';
}
}
} }
enum SkillOption { enum SkillOption {

View File

@ -64,7 +64,7 @@ class SkillsForm extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
// set following registration page HERE // set following registration page HERE
builder: (context) => builder: (context) =>
MatchingForm(isRegProcess: isRegProcess), UserVisionPage(isRegProcess: isRegProcess),
), ),
); );
} else if (isRegProcess) { } else if (isRegProcess) {

View File

@ -1,6 +1,7 @@
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import '../constants.dart'; import '../constants.dart';
import '../enumerations.dart';
import 'language.dart'; import 'language.dart';
import 'location.dart'; import 'location.dart';
@ -13,11 +14,13 @@ class UserProfile {
final String lastName; final String lastName;
String? profilePictureUrl; String? profilePictureUrl;
String? bio; String? bio;
Gender? gender;
int? born;
final String risk; final String risk;
final List<String> skills; final List<String> skills;
final List<String> skillsSought; final List<String> skillsSought;
final List<Language> languages; List<Language> languages;
final Map<String, MyLocation?> locations; Map<String, MyLocation?> locations;
UserProfile({ UserProfile({
required this.id, required this.id,
@ -28,6 +31,8 @@ class UserProfile {
required this.lastName, required this.lastName,
this.profilePictureUrl, this.profilePictureUrl,
this.bio, this.bio,
this.gender,
this.born,
required this.risk, required this.risk,
required this.skills, required this.skills,
required this.skillsSought, required this.skillsSought,
@ -51,6 +56,8 @@ class UserProfile {
risk: data[Constants.dbFieldUsersRiskTolerance] ?? '', risk: data[Constants.dbFieldUsersRiskTolerance] ?? '',
profilePictureUrl: data[Constants.dbFieldUsersProfilePic], profilePictureUrl: data[Constants.dbFieldUsersProfilePic],
bio: data[Constants.dbFieldUsersBio], bio: data[Constants.dbFieldUsersBio],
gender: Gender.values[data[Constants.dbFieldUsersGender] ?? 0],
born: data[Constants.dbFieldUsersYearBorn],
languages: [], languages: [],
locations: {}, locations: {},
); );

View File

@ -3,9 +3,9 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../components/location_dialog.dart'; import '../components/location_dialog.dart';
import '../components/my_button.dart'; import '../components/my_button.dart';
import '../components/my_drawer.dart';
import '../constants.dart'; import '../constants.dart';
import '../enumerations.dart'; import '../enumerations.dart';
import '../forms/skills_form.dart'; import '../forms/skills_form.dart';
@ -13,11 +13,14 @@ import '../models/language.dart';
import '../models/language_setting.dart'; import '../models/language_setting.dart';
import '../models/location.dart'; import '../models/location.dart';
import '../services/auth/auth_service.dart'; import '../services/auth/auth_service.dart';
import '../utils/math.dart';
class UserDataPage extends StatefulWidget { class UserDataPage extends StatefulWidget {
final bool isRegProcess; final bool isRegProcess;
final bool isEditMode;
const UserDataPage({super.key, required this.isRegProcess}); const UserDataPage(
{super.key, required this.isRegProcess, required this.isEditMode});
@override @override
State<UserDataPage> createState() => _UserDataPageState(); State<UserDataPage> createState() => _UserDataPageState();
@ -87,14 +90,14 @@ class _UserDataPageState extends State<UserDataPage> {
MyLocation userLoc; MyLocation userLoc;
for (var doc in locationSnapshot.docs) { for (var doc in locationSnapshot.docs) {
userLoc = MyLocation( userLoc = MyLocation(
street: doc['street'], street: doc[Constants.dbFieldLocationStreet],
country: doc['country'], country: doc[Constants.dbFieldLocationCountry],
administrativeArea: doc['administrativeArea'], administrativeArea: doc[Constants.dbFieldLocationArea],
locality: doc['locality'], locality: doc[Constants.dbFieldLocationLocality],
subLocality: doc['subLocality'], subLocality: doc[Constants.dbFieldLocationSubLocality],
postalCode: doc['postalCode'], postalCode: doc[Constants.dbFieldLocationPostalCode],
latitude: doc['latitude'], latitude: doc[Constants.dbFieldLocationLatitude],
longitude: doc['longitude'], longitude: doc[Constants.dbFieldLocationLongitude],
); );
if (doc.id == Constants.dbDocMainLocation) { if (doc.id == Constants.dbDocMainLocation) {
_mainLocationFromDb = userLoc; _mainLocationFromDb = userLoc;
@ -132,7 +135,7 @@ class _UserDataPageState extends State<UserDataPage> {
// Load data from JSON file when the widget initializes // Load data from JSON file when the widget initializes
loadLanguagesFromJson(); loadLanguagesFromJson();
} catch (error) { } catch (error) {
_showSnackBar("Error fetching settings: $error"); _showSnackBar('Error fetching settings: $error');
} }
} }
@ -225,14 +228,14 @@ class _UserDataPageState extends State<UserDataPage> {
_secondaryLocationFromDb = null, _secondaryLocationFromDb = null,
_secondLocationExists = false _secondLocationExists = false
}, },
onError: (e) => _showSnackBar("Error updating document: $e"), onError: (e) => _showSnackBar('Error updating document: $e'),
); );
} }
} }
// Save Languages - only if selected values changed // Save Languages - only if selected values changed
if (_selectedLanguages.isEmpty) { if (_selectedLanguages.isEmpty) {
_showSnackBar("No language selected"); _showSnackBar('No language selected');
return false; return false;
} else if (_languagesFromDb.length != _selectedLanguages.length || } else if (_languagesFromDb.length != _selectedLanguages.length ||
_selectedLanguages _selectedLanguages
@ -315,6 +318,50 @@ class _UserDataPageState extends State<UserDataPage> {
}); });
} }
void _saveButtonClicked(BuildContext context) async {
bool success = await saveUserData(context);
if (context.mounted) {
if (success) {
if (widget.isRegProcess) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SkillsForm(
isRegProcess: widget.isRegProcess,
skillsSought: false,
),
),
);
} else {
if (widget.isEditMode == true) {
// pass data back to caller
Map<String, MyLocation?> locations = {
if (_mainLocation != null)
Constants.dbDocMainLocation: _mainLocation,
if (_secondaryLocation != null)
Constants.dbDocSecondLocation: _secondaryLocation,
};
Navigator.pop(context, {
Constants.dbFieldUsersYearBorn: _selectedYear,
Constants.dbFieldUsersGender: genderView,
Constants.dbCollectionLanguages: _languagesFromDb,
Constants.dbCollectionLocations: locations,
});
} else {
Navigator.pop(context);
}
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to save user data.'),
),
);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (languagesList.isEmpty) { if (languagesList.isEmpty) {
@ -322,10 +369,18 @@ class _UserDataPageState extends State<UserDataPage> {
} else { } else {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("User Data"), title:
Text('${widget.isRegProcess ? 'User Data' : 'Edit your data'} '),
centerTitle: true, centerTitle: true,
actions: [
if (widget.isRegProcess)
IconButton(
onPressed: () {
AuthService().signOut();
},
icon: const Icon(Icons.logout))
],
), ),
drawer: const MyDrawer(),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: ListView( child: ListView(
@ -409,7 +464,7 @@ class _UserDataPageState extends State<UserDataPage> {
children: [ children: [
const Padding(padding: EdgeInsets.symmetric(horizontal: 8)), const Padding(padding: EdgeInsets.symmetric(horizontal: 8)),
Text(_selectedYear != null Text(_selectedYear != null
? '${DateTime.now().year - (_selectedYear ?? 0)} years old' ? '${calcAge(_selectedYear)} years old'
: 'undefined age'), : 'undefined age'),
const SizedBox(width: 20), const SizedBox(width: 20),
DropdownMenu( DropdownMenu(
@ -457,7 +512,7 @@ class _UserDataPageState extends State<UserDataPage> {
), ),
ButtonSegment<Gender>( ButtonSegment<Gender>(
value: Gender.divers, value: Gender.divers,
label: Text('divers'), label: Text('diverse'),
), ),
], ],
selected: <Gender>{genderView}, selected: <Gender>{genderView},
@ -477,32 +532,9 @@ class _UserDataPageState extends State<UserDataPage> {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: MyButton( child: MyButton(
text: widget.isRegProcess ? 'Save and continue' : "Save", text: widget.isRegProcess ? 'Save and continue' : 'Save',
onTap: () async { onTap: () {
bool success = await saveUserData(context); _saveButtonClicked(context);
if (context.mounted) {
if (success) {
if (widget.isRegProcess) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SkillsForm(
isRegProcess: widget.isRegProcess,
skillsSought: false,
),
),
);
} else {
Navigator.pop(context);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to save user data.'),
),
);
}
}
}, },
), ),
), ),

View File

@ -4,7 +4,10 @@ import 'package:firebase_auth/firebase_auth.dart';
import '../constants.dart'; import '../constants.dart';
import '../models/user_profile.dart'; import '../models/user_profile.dart';
import '../services/user_service.dart'; import '../services/user_service.dart';
import '../utils/helper.dart';
import '../utils/math.dart';
import 'edit_profile_page.dart'; import 'edit_profile_page.dart';
import 'user_data_page.dart';
class UserProfilePage extends StatefulWidget { class UserProfilePage extends StatefulWidget {
const UserProfilePage({super.key}); const UserProfilePage({super.key});
@ -57,6 +60,26 @@ class _UserProfilePageState extends State<UserProfilePage> {
} }
} }
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];
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -70,47 +93,13 @@ class _UserProfilePageState extends State<UserProfilePage> {
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Align( _buildAvatar(context),
alignment: Alignment.bottomRight,
child: OutlinedButton.icon(
label: const Text('Edit'),
icon: const Icon(Icons.edit),
onPressed: editNameInfo,
),
),
CircleAvatar(
radius: 50,
backgroundImage: profileImageUrl != null
? NetworkImage(profileImageUrl!)
: null,
child: profileImageUrl == null
? const Icon(Icons.person, size: 50)
: null,
),
const SizedBox(height: 16), const SizedBox(height: 16),
Text(myData.name, style: const TextStyle(fontSize: 24)), Divider(color: Theme.of(context).colorScheme.primary),
Text(myData.email, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 32),
Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Short description of yourself',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
Text(myData.bio ?? 'N/A',
style: const TextStyle(fontSize: 16)),
],
),
),
const SizedBox(height: 16), const SizedBox(height: 16),
Divider( _buildLocation(context),
color: Theme.of(context).colorScheme.primary, const SizedBox(height: 16),
), Divider(color: Theme.of(context).colorScheme.primary),
const SizedBox(height: 16), const SizedBox(height: 16),
], ],
), ),
@ -118,4 +107,130 @@ class _UserProfilePageState extends State<UserProfilePage> {
), ),
); );
} }
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(
'Age',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
Text(
(age > 0
? '$age years old, born ${myData.born}'
: 'n/a'),
style: const TextStyle(fontSize: 16)),
],
),
Align(
alignment: Alignment.bottomRight,
child: OutlinedButton.icon(
label: const Text('Edit'),
icon: const Icon(Icons.edit),
onPressed: editUserDataInfo,
),
),
],
),
const SizedBox(height: 16),
Text(
'Gender',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
Text(getDisplayText(myData.gender),
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 16),
Text(
'Spoken languages',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
Text(
myData.languages.map((lang) => lang.name).join(', '),
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
Text(
'Locations',
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),
),
],
),
),
],
);
}
Widget _buildAvatar(BuildContext context) {
return Column(
children: [
Align(
alignment: Alignment.bottomRight,
child: OutlinedButton.icon(
label: const Text('Edit'),
icon: const Icon(Icons.edit),
onPressed: editNameInfo,
),
),
CircleAvatar(
radius: 50,
backgroundImage:
profileImageUrl != null ? NetworkImage(profileImageUrl!) : null,
child: profileImageUrl == null
? const Icon(Icons.person, size: 50)
: null,
),
const SizedBox(height: 16),
Text(myData.name, style: const TextStyle(fontSize: 24)),
Text(myData.email, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 32),
Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Short description of yourself',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
Text(myData.bio ?? 'n/a', style: const TextStyle(fontSize: 16)),
],
),
),
],
);
}
} }

View File

@ -6,16 +6,16 @@ import '../enumerations.dart';
import '../forms/corporate_culture_form.dart'; import '../forms/corporate_culture_form.dart';
import '../services/auth/auth_service.dart'; import '../services/auth/auth_service.dart';
class MatchingForm extends StatefulWidget { class UserVisionPage extends StatefulWidget {
const MatchingForm({super.key, required this.isRegProcess}); const UserVisionPage({super.key, required this.isRegProcess});
final bool isRegProcess; final bool isRegProcess;
@override @override
MatchingFormState createState() => MatchingFormState(); UserVisionPageState createState() => UserVisionPageState();
} }
class MatchingFormState extends State<MatchingForm> { class UserVisionPageState extends State<UserVisionPage> {
Map<VisionOption, bool> selectedVisionOptions = { Map<VisionOption, bool> selectedVisionOptions = {
VisionOption.marketLeader: false, VisionOption.marketLeader: false,
VisionOption.sustainableBusiness: false, VisionOption.sustainableBusiness: false,

View File

@ -37,7 +37,10 @@ class AuthGate extends StatelessWidget {
return HomePage(); return HomePage();
} else { } else {
// also in case of (snapshot.hasError) // also in case of (snapshot.hasError)
return const UserDataPage(isRegProcess: true); return const UserDataPage(
isRegProcess: true,
isEditMode: false,
);
} }
}, },
); );
@ -86,8 +89,8 @@ class AuthGate extends StatelessWidget {
.collection(Constants.dbCollectionUsers) .collection(Constants.dbCollectionUsers)
.doc(currentUserId) .doc(currentUserId)
.set({ .set({
'uid': currentUserId, Constants.dbFieldUsersID: currentUserId,
'email': email, Constants.dbFieldUsersEmail: email,
}); });
} catch (e) { } catch (e) {
print("Error creating user document: $e"); print("Error creating user document: $e");

View File

@ -2,6 +2,14 @@ import 'dart:math';
import '../models/user_profile.dart'; import '../models/user_profile.dart';
/// Approximate determination of age
int calcAge(int? birthYear) {
if (birthYear != null) {
return (DateTime.now().year - birthYear);
}
return 0;
}
/// ///
/// Convert decimal coordinate to degrees minutes seconds (DMS). /// Convert decimal coordinate to degrees minutes seconds (DMS).
/// ///