diff --git a/lib/components/my_drawer.dart b/lib/components/my_drawer.dart index b06fd4e..5b38d05 100644 --- a/lib/components/my_drawer.dart +++ b/lib/components/my_drawer.dart @@ -136,6 +136,7 @@ class MyDrawer extends StatelessWidget { MaterialPageRoute( builder: (context) => const UserDataPage( isRegProcess: false, + isEditMode: false, ), ), ); diff --git a/lib/enumerations.dart b/lib/enumerations.dart index 280f3bf..2e5b5c6 100644 --- a/lib/enumerations.dart +++ b/lib/enumerations.dart @@ -2,7 +2,20 @@ enum Gender { none, male, 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 { diff --git a/lib/forms/skills_form.dart b/lib/forms/skills_form.dart index 8d9379e..29ceaeb 100644 --- a/lib/forms/skills_form.dart +++ b/lib/forms/skills_form.dart @@ -64,7 +64,7 @@ class SkillsForm extends StatelessWidget { MaterialPageRoute( // set following registration page HERE builder: (context) => - MatchingForm(isRegProcess: isRegProcess), + UserVisionPage(isRegProcess: isRegProcess), ), ); } else if (isRegProcess) { diff --git a/lib/models/user_profile.dart b/lib/models/user_profile.dart index 6579b87..80de077 100644 --- a/lib/models/user_profile.dart +++ b/lib/models/user_profile.dart @@ -1,6 +1,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import '../constants.dart'; +import '../enumerations.dart'; import 'language.dart'; import 'location.dart'; @@ -13,11 +14,13 @@ class UserProfile { final String lastName; String? profilePictureUrl; String? bio; + Gender? gender; + int? born; final String risk; final List skills; final List skillsSought; - final List languages; - final Map locations; + List languages; + Map locations; UserProfile({ required this.id, @@ -28,6 +31,8 @@ class UserProfile { required this.lastName, this.profilePictureUrl, this.bio, + this.gender, + this.born, required this.risk, required this.skills, required this.skillsSought, @@ -51,6 +56,8 @@ class UserProfile { risk: data[Constants.dbFieldUsersRiskTolerance] ?? '', profilePictureUrl: data[Constants.dbFieldUsersProfilePic], bio: data[Constants.dbFieldUsersBio], + gender: Gender.values[data[Constants.dbFieldUsersGender] ?? 0], + born: data[Constants.dbFieldUsersYearBorn], languages: [], locations: {}, ); diff --git a/lib/pages/user_data_page.dart b/lib/pages/user_data_page.dart index f17ace9..025a29d 100644 --- a/lib/pages/user_data_page.dart +++ b/lib/pages/user_data_page.dart @@ -3,9 +3,9 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; + import '../components/location_dialog.dart'; import '../components/my_button.dart'; -import '../components/my_drawer.dart'; import '../constants.dart'; import '../enumerations.dart'; import '../forms/skills_form.dart'; @@ -13,11 +13,14 @@ import '../models/language.dart'; import '../models/language_setting.dart'; import '../models/location.dart'; import '../services/auth/auth_service.dart'; +import '../utils/math.dart'; class UserDataPage extends StatefulWidget { final bool isRegProcess; + final bool isEditMode; - const UserDataPage({super.key, required this.isRegProcess}); + const UserDataPage( + {super.key, required this.isRegProcess, required this.isEditMode}); @override State createState() => _UserDataPageState(); @@ -87,14 +90,14 @@ class _UserDataPageState extends State { MyLocation userLoc; for (var doc in locationSnapshot.docs) { userLoc = MyLocation( - street: doc['street'], - country: doc['country'], - administrativeArea: doc['administrativeArea'], - locality: doc['locality'], - subLocality: doc['subLocality'], - postalCode: doc['postalCode'], - latitude: doc['latitude'], - longitude: doc['longitude'], + street: doc[Constants.dbFieldLocationStreet], + country: doc[Constants.dbFieldLocationCountry], + administrativeArea: doc[Constants.dbFieldLocationArea], + locality: doc[Constants.dbFieldLocationLocality], + subLocality: doc[Constants.dbFieldLocationSubLocality], + postalCode: doc[Constants.dbFieldLocationPostalCode], + latitude: doc[Constants.dbFieldLocationLatitude], + longitude: doc[Constants.dbFieldLocationLongitude], ); if (doc.id == Constants.dbDocMainLocation) { _mainLocationFromDb = userLoc; @@ -132,7 +135,7 @@ class _UserDataPageState extends State { // Load data from JSON file when the widget initializes loadLanguagesFromJson(); } catch (error) { - _showSnackBar("Error fetching settings: $error"); + _showSnackBar('Error fetching settings: $error'); } } @@ -225,14 +228,14 @@ class _UserDataPageState extends State { _secondaryLocationFromDb = null, _secondLocationExists = false }, - onError: (e) => _showSnackBar("Error updating document: $e"), + onError: (e) => _showSnackBar('Error updating document: $e'), ); } } // Save Languages - only if selected values changed if (_selectedLanguages.isEmpty) { - _showSnackBar("No language selected"); + _showSnackBar('No language selected'); return false; } else if (_languagesFromDb.length != _selectedLanguages.length || _selectedLanguages @@ -315,6 +318,50 @@ class _UserDataPageState extends State { }); } + 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 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 Widget build(BuildContext context) { if (languagesList.isEmpty) { @@ -322,10 +369,18 @@ class _UserDataPageState extends State { } else { return Scaffold( appBar: AppBar( - title: const Text("User Data"), + title: + Text('${widget.isRegProcess ? 'User Data' : 'Edit your data'} '), centerTitle: true, + actions: [ + if (widget.isRegProcess) + IconButton( + onPressed: () { + AuthService().signOut(); + }, + icon: const Icon(Icons.logout)) + ], ), - drawer: const MyDrawer(), body: Padding( padding: const EdgeInsets.all(16.0), child: ListView( @@ -409,7 +464,7 @@ class _UserDataPageState extends State { children: [ const Padding(padding: EdgeInsets.symmetric(horizontal: 8)), Text(_selectedYear != null - ? '${DateTime.now().year - (_selectedYear ?? 0)} years old' + ? '${calcAge(_selectedYear)} years old' : 'undefined age'), const SizedBox(width: 20), DropdownMenu( @@ -457,7 +512,7 @@ class _UserDataPageState extends State { ), ButtonSegment( value: Gender.divers, - label: Text('divers'), + label: Text('diverse'), ), ], selected: {genderView}, @@ -477,32 +532,9 @@ class _UserDataPageState extends State { Padding( padding: const EdgeInsets.all(8.0), child: MyButton( - text: widget.isRegProcess ? 'Save and continue' : "Save", - onTap: () 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 { - Navigator.pop(context); - } - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Failed to save user data.'), - ), - ); - } - } + text: widget.isRegProcess ? 'Save and continue' : 'Save', + onTap: () { + _saveButtonClicked(context); }, ), ), diff --git a/lib/pages/user_profile_page.dart b/lib/pages/user_profile_page.dart index da0e052..aec307a 100644 --- a/lib/pages/user_profile_page.dart +++ b/lib/pages/user_profile_page.dart @@ -4,7 +4,10 @@ import 'package:firebase_auth/firebase_auth.dart'; import '../constants.dart'; import '../models/user_profile.dart'; import '../services/user_service.dart'; +import '../utils/helper.dart'; +import '../utils/math.dart'; import 'edit_profile_page.dart'; +import 'user_data_page.dart'; class UserProfilePage extends StatefulWidget { const UserProfilePage({super.key}); @@ -57,6 +60,26 @@ class _UserProfilePageState extends State { } } + 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 Widget build(BuildContext context) { return Scaffold( @@ -70,47 +93,13 @@ class _UserProfilePageState extends State { child: SingleChildScrollView( child: 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, - ), + _buildAvatar(context), 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)), - ], - ), - ), + Divider(color: Theme.of(context).colorScheme.primary), const SizedBox(height: 16), - Divider( - color: Theme.of(context).colorScheme.primary, - ), + _buildLocation(context), + const SizedBox(height: 16), + Divider(color: Theme.of(context).colorScheme.primary), const SizedBox(height: 16), ], ), @@ -118,4 +107,130 @@ class _UserProfilePageState extends State { ), ); } + + 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)), + ], + ), + ), + ], + ); + } } diff --git a/lib/pages/user_vision_page.dart b/lib/pages/user_vision_page.dart index 0430768..fa78dda 100644 --- a/lib/pages/user_vision_page.dart +++ b/lib/pages/user_vision_page.dart @@ -6,16 +6,16 @@ import '../enumerations.dart'; import '../forms/corporate_culture_form.dart'; import '../services/auth/auth_service.dart'; -class MatchingForm extends StatefulWidget { - const MatchingForm({super.key, required this.isRegProcess}); +class UserVisionPage extends StatefulWidget { + const UserVisionPage({super.key, required this.isRegProcess}); final bool isRegProcess; @override - MatchingFormState createState() => MatchingFormState(); + UserVisionPageState createState() => UserVisionPageState(); } -class MatchingFormState extends State { +class UserVisionPageState extends State { Map selectedVisionOptions = { VisionOption.marketLeader: false, VisionOption.sustainableBusiness: false, diff --git a/lib/services/auth/auth_gate.dart b/lib/services/auth/auth_gate.dart index b2e783f..1e1c88f 100644 --- a/lib/services/auth/auth_gate.dart +++ b/lib/services/auth/auth_gate.dart @@ -37,7 +37,10 @@ class AuthGate extends StatelessWidget { return HomePage(); } else { // 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) .doc(currentUserId) .set({ - 'uid': currentUserId, - 'email': email, + Constants.dbFieldUsersID: currentUserId, + Constants.dbFieldUsersEmail: email, }); } catch (e) { print("Error creating user document: $e"); diff --git a/lib/utils/math.dart b/lib/utils/math.dart index b7791f1..d3be612 100644 --- a/lib/utils/math.dart +++ b/lib/utils/math.dart @@ -2,6 +2,14 @@ import 'dart:math'; 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). ///