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(
builder: (context) => const UserDataPage(
isRegProcess: false,
isEditMode: false,
),
),
);

View File

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

View File

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

View File

@ -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<String> skills;
final List<String> skillsSought;
final List<Language> languages;
final Map<String, MyLocation?> locations;
List<Language> languages;
Map<String, MyLocation?> 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: {},
);

View File

@ -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<UserDataPage> createState() => _UserDataPageState();
@ -87,14 +90,14 @@ class _UserDataPageState extends State<UserDataPage> {
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<UserDataPage> {
// 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<UserDataPage> {
_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<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
Widget build(BuildContext context) {
if (languagesList.isEmpty) {
@ -322,10 +369,18 @@ class _UserDataPageState extends State<UserDataPage> {
} 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<UserDataPage> {
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<UserDataPage> {
),
ButtonSegment<Gender>(
value: Gender.divers,
label: Text('divers'),
label: Text('diverse'),
),
],
selected: <Gender>{genderView},
@ -477,32 +532,9 @@ class _UserDataPageState extends State<UserDataPage> {
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);
},
),
),

View File

@ -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<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
Widget build(BuildContext context) {
return Scaffold(
@ -70,47 +93,13 @@ class _UserProfilePageState extends State<UserProfilePage> {
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<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 '../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<MatchingForm> {
class UserVisionPageState extends State<UserVisionPage> {
Map<VisionOption, bool> selectedVisionOptions = {
VisionOption.marketLeader: false,
VisionOption.sustainableBusiness: false,

View File

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

View File

@ -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).
///