Added Sectors of Interest and Save Button to AppBars.

master
Rafael 2024-06-20 22:47:54 +02:00
parent e79bfd5b7e
commit 87beb2dace
11 changed files with 340 additions and 82 deletions

View File

@ -54,6 +54,7 @@ class Constants {
static const String dbFieldUsersCorpCulture = 'corp_culture'; static const String dbFieldUsersCorpCulture = 'corp_culture';
static const String dbFieldUsersCommunication = 'communication'; static const String dbFieldUsersCommunication = 'communication';
static const String dbFieldUsersRiskTolerance = 'risk_tolerance'; static const String dbFieldUsersRiskTolerance = 'risk_tolerance';
static const String dbFieldUsersSectors = 'sectors_of_interest';
static const String dbStoragePathProfiles = 'profile_images'; static const String dbStoragePathProfiles = 'profile_images';

View File

@ -252,3 +252,98 @@ enum RiskTolerance {
); );
} }
} }
enum SectorOption {
agriculture,
ai,
vr,
automotive,
bigdata,
blockchain,
cloud,
crypto,
cybersecurity,
education,
energy,
fashion,
finance,
food,
gaming,
healthcare,
home,
industry,
insurance,
iot,
legal,
ml,
media,
pharma,
retail,
robotics,
saas,
sports,
travel;
String get displayName {
switch (this) {
case SectorOption.agriculture:
return 'Agriculture';
case SectorOption.ai:
return 'Artificial Intelligence';
case SectorOption.vr:
return 'Augmented/Virtual Reality';
case SectorOption.automotive:
return 'Automotive';
case SectorOption.bigdata:
return 'Big Data';
case SectorOption.blockchain:
return 'Blockchain';
case SectorOption.cloud:
return 'Cloud Computing';
case SectorOption.crypto:
return 'Cryptocurrencies/Digital Assets';
case SectorOption.cybersecurity:
return 'Cybersecurity';
case SectorOption.education:
return 'Education';
case SectorOption.energy:
return 'Energy and Environment';
case SectorOption.fashion:
return 'Fashion and Lifestyle';
case SectorOption.finance:
return 'Finance';
case SectorOption.food:
return 'Food and Beverage';
case SectorOption.gaming:
return 'Gaming';
case SectorOption.healthcare:
return 'Healthcare';
case SectorOption.home:
return 'Home and Garden';
case SectorOption.industry:
return 'Industry';
case SectorOption.insurance:
return 'Insurance';
case SectorOption.iot:
return 'Internet of Things';
case SectorOption.legal:
return 'Legal and Compliance';
case SectorOption.ml:
return 'Machine Learning';
case SectorOption.media:
return 'Media and Entertainment';
case SectorOption.pharma:
return 'Pharma';
case SectorOption.retail:
return 'Retail';
case SectorOption.robotics:
return 'Robotics';
case SectorOption.saas:
return 'SaaS';
case SectorOption.sports:
return 'Sports and Fitness';
case SectorOption.travel:
return 'Travel';
}
}
}

View File

@ -151,6 +151,13 @@ class CultureValuesFormPageState extends State<CultureValuesFormPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Personal preferences'), title: const Text('Personal preferences'),
actions: [
if (widget.isEditMode && !widget.isRegProcess)
IconButton(
onPressed: handleSubmit,
icon: const Icon(Icons.save),
)
],
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),

View File

@ -25,7 +25,7 @@ class ProfileCategoryForm<T> extends StatefulWidget {
required this.onSave, required this.onSave,
required this.preSelectedOptions, required this.preSelectedOptions,
this.saveButtonText, this.saveButtonText,
this.hideSaveButton = false, // Initialize hideSaveButton parameter this.hideSaveButton = false,
}); });
@override @override
@ -47,6 +47,13 @@ class ProfileCategoryFormState<T> extends State<ProfileCategoryForm<T>> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(widget.title), title: Text(widget.title),
actions: [
if (!widget.hideSaveButton)
IconButton(
onPressed: _saveButtonClicked,
icon: const Icon(Icons.save),
)
],
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
@ -70,9 +77,9 @@ class ProfileCategoryFormState<T> extends State<ProfileCategoryForm<T>> {
if (_selectedOptions.length < widget.maxSelections) { if (_selectedOptions.length < widget.maxSelections) {
_selectedOptions.add(option); _selectedOptions.add(option);
} else { } else {
// Provide feedback that maximum selections reached
_showSnackBar( _showSnackBar(
'Maximum selections reached for ${widget.title}'); 'Maximum selections reached for ${widget.title}',
);
} }
} else { } else {
_selectedOptions.remove(option); _selectedOptions.remove(option);
@ -85,13 +92,7 @@ class ProfileCategoryFormState<T> extends State<ProfileCategoryForm<T>> {
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
if (!widget.hideSaveButton) if (!widget.hideSaveButton)
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: _saveButtonClicked,
if (_selectedOptions.length >= widget.minSelections) {
widget.onSave(_selectedOptions);
} else {
_showSnackBar('No selection made for ${widget.title}');
}
},
child: Text(widget.saveButtonText ?? 'Save'), child: Text(widget.saveButtonText ?? 'Save'),
), ),
], ],
@ -100,6 +101,14 @@ class ProfileCategoryFormState<T> extends State<ProfileCategoryForm<T>> {
); );
} }
void _saveButtonClicked() {
if (_selectedOptions.length >= widget.minSelections) {
widget.onSave(_selectedOptions);
} else {
_showSnackBar('No selection made for ${widget.title}');
}
}
void _showSnackBar(String message) { void _showSnackBar(String message) {
if (!_isSnackBarVisible) { if (!_isSnackBarVisible) {
_isSnackBarVisible = true; _isSnackBarVisible = true;

View File

@ -129,6 +129,13 @@ class RisksFormPageState extends State<RisksFormPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Personal preferences'), title: const Text('Personal preferences'),
actions: [
if (widget.isEditMode && !widget.isRegProcess)
IconButton(
onPressed: handleSubmit,
icon: const Icon(Icons.save),
)
],
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import '../../enumerations.dart';
import '../../services/auth/auth_service.dart';
import '../../services/user_service.dart';
import '../constants.dart';
import '../pages/user_vision_page.dart';
import 'profile_category_form.dart';
class SectorsForm extends StatelessWidget {
SectorsForm({
super.key,
required this.isRegProcess,
required this.isEditMode,
});
final AuthService _authService = AuthService();
final bool isRegProcess;
final bool isEditMode;
@override
Widget build(BuildContext context) {
return FutureBuilder<List<SectorOption>>(
future: UserService.getSectorsFromFirebase(
_authService.getCurrentUser()!.uid,
),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<SectorOption>? userSectors = snapshot.data;
int max = 10;
return ProfileCategoryForm(
title: 'Sectors of interest',
header: 'Sectors of interest',
description: 'Select up to $max sectors that match your interests.',
saveButtonText: isRegProcess ? 'Save and continue' : 'Save',
options: SectorOption.values.toList(), // Convert enum to list
minSelections: 1,
maxSelections: max,
preSelectedOptions: userSectors ?? [],
onSave: (selectedOptions) async {
// Handle saving selected options
bool success = await UserService.saveSectorsToFirebase(
selectedOptions.cast<SectorOption>(),
_authService.getCurrentUser()!.uid,
);
if (context.mounted) {
if (success) {
if (isRegProcess) {
Navigator.push(
context,
MaterialPageRoute(
// set following registration page HERE
builder: (context) => UserVisionPage(
isRegProcess: isRegProcess,
isEditMode: false,
),
),
);
} else {
if (isEditMode == true) {
// pass selectedOptions data back to caller
Navigator.pop(context, {
Constants.dbFieldUsersSectors: selectedOptions,
});
} else {
// Navigate back after saving
Navigator.pop(context);
}
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to save sectors of interest.'),
),
);
}
}
},
);
}
},
);
}
}

View File

@ -3,8 +3,8 @@ import '../../enumerations.dart';
import '../../services/auth/auth_service.dart'; import '../../services/auth/auth_service.dart';
import '../../services/user_service.dart'; import '../../services/user_service.dart';
import '../constants.dart'; import '../constants.dart';
import '../pages/user_vision_page.dart';
import 'profile_category_form.dart'; import 'profile_category_form.dart';
import 'sectors_form.dart';
class SkillsForm extends StatelessWidget { class SkillsForm extends StatelessWidget {
SkillsForm({ SkillsForm({
@ -14,7 +14,6 @@ class SkillsForm extends StatelessWidget {
required this.isEditMode, required this.isEditMode,
}); });
// get instance of auth
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
final bool isRegProcess; final bool isRegProcess;
@ -27,7 +26,7 @@ class SkillsForm extends StatelessWidget {
future: UserService.getSkillsFromFirebase( future: UserService.getSkillsFromFirebase(
skillsSought, skillsSought,
_authService.getCurrentUser()!.uid, _authService.getCurrentUser()!.uid,
), // Fetch skills from Firebase ),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
// Show loading indicator while fetching data // Show loading indicator while fetching data
@ -58,7 +57,7 @@ class SkillsForm extends StatelessWidget {
_authService.getCurrentUser()!.uid, _authService.getCurrentUser()!.uid,
); );
// Then navigate to another screen or perform any other action??? // Then navigate to another screen or perform any other action
if (context.mounted) { if (context.mounted) {
if (success) { if (success) {
if (isRegProcess && skillsSought) { if (isRegProcess && skillsSought) {
@ -66,7 +65,7 @@ class SkillsForm extends StatelessWidget {
context, context,
MaterialPageRoute( MaterialPageRoute(
// set following registration page HERE // set following registration page HERE
builder: (context) => UserVisionPage( builder: (context) => SectorsForm(
isRegProcess: isRegProcess, isRegProcess: isRegProcess,
isEditMode: false, isEditMode: false,
), ),

View File

@ -160,6 +160,12 @@ class EditProfilePageState extends State<EditProfilePage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Edit Profile'), title: const Text('Edit Profile'),
actions: [
IconButton(
onPressed: _saveProfile,
icon: const Icon(Icons.save),
)
],
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),

View File

@ -149,6 +149,13 @@ class UserVisionPageState extends State<UserVisionPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Personal preferences'), title: const Text('Personal preferences'),
actions: [
if (widget.isEditMode && !widget.isRegProcess)
IconButton(
onPressed: handleSubmit,
icon: const Icon(Icons.save),
)
],
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),

View File

@ -10,73 +10,73 @@ class WelcomePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: const Text('Welcome to ${Constants.appTitle}'),),
title: const Text('Start now'), body: SafeArea(
), child: Padding(
body: Padding( padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16.0), child: SingleChildScrollView(
child: SingleChildScrollView( child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ const TextBold(
const TextBold( text: 'Connect with Potential Co-Founders Here',
text: 'Welcome to ${Constants.appTitle}', fontSize: 24,
fontSize: 24,
),
const SizedBox(height: 16),
const Text(
'The goal of this app is to help you find the ideal co-founder for your startup. '
'It aims to connect people with similar goals, complementary '
'skills, and shared interests to create successful partnerships.',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
const Text(
'To achieve this and get the most out of the app, '
'you will be asked to share some information about yourself '
'in the next steps. This includes the following details:\n'
'\u25CF Personal Information: Your name, year of birth, and spoken languages will help match you with others.\n'
'\u25CF Your Location: To suggest and find co-founders near you.\n'
'\u25CF Your Skills and Experiences: To find the ideal co-founder with complementary skills.\n'
'\u25CF Your Goals and Interests: To ensure you share a similar vision.\n',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 24),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () {
AuthService().signOut();
},
label: const Text('Sign out'),
icon: const Icon(Icons.logout),
),
const SizedBox(
width: 16,
),
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
const UserDataPage(
isRegProcess: true,
isEditMode: false,
),
),
);
},
label: const Text('Continue'),
icon: const Icon(Icons.navigate_next),
iconAlignment: IconAlignment.start,
),
],
), ),
), const SizedBox(height: 16),
], const Text(
'The goal of this app is to help you find the ideal co-founder for your startup. '
'It aims to connect people with similar goals, complementary '
'skills, and shared interests to create successful partnerships.',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
const Text(
'To achieve this and get the most out of the app, '
'you will be asked to share some information about yourself '
'in the next steps. This includes the following details:\n'
'\u25CF Personal Information: Your name, year of birth, and spoken languages will help match you with others.\n'
'\u25CF Your Location: To suggest and find co-founders near you.\n'
'\u25CF Your Skills and Experiences: To find the ideal co-founder with complementary skills.\n'
'\u25CF Your Goals and Interests: To ensure you share a similar vision.\n',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 24),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () {
AuthService().signOut();
},
label: const Text('Sign out'),
icon: const Icon(Icons.logout),
),
const SizedBox(
width: 16,
),
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
const UserDataPage(
isRegProcess: true,
isEditMode: false,
),
),
);
},
label: const Text('Continue'),
icon: const Icon(Icons.navigate_next),
iconAlignment: IconAlignment.start,
),
],
),
),
],
),
), ),
), ),
), ),

View File

@ -272,7 +272,7 @@ class UserService {
/// Checks if a match between currentUser and [userId] exists. /// Checks if a match between currentUser and [userId] exists.
/// Returns false in case of an error. /// Returns false in case of an error.
static Future<bool> hasMatch(String currentUserId, String userId) async { static Future<bool> hasMatch(String currentUserId, String userId) async {
try { try {
DocumentSnapshot matchDoc = await FirebaseFirestore.instance DocumentSnapshot matchDoc = await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers) .collection(Constants.dbCollectionUsers)
.doc(currentUserId) .doc(currentUserId)
@ -284,4 +284,42 @@ class UserService {
return false; return false;
} }
} }
static Future<bool> saveSectorsToFirebase(
List<SectorOption> sectors, String userId) async {
try {
List<String> sectorStrings = sectors.map((x) => x.toString()).toList();
await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(userId)
.update({
Constants.dbFieldUsersSectors: sectorStrings,
});
return true;
} catch (e) {
return false;
}
}
static Future<List<SectorOption>> getSectorsFromFirebase(
String userId) async {
try {
DocumentSnapshot userDoc = await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(userId)
.get();
if (userDoc.exists && userDoc.data() != null) {
var data = userDoc.data() as Map<String, dynamic>;
List<dynamic> sectors = data[Constants.dbFieldUsersSectors] ?? [];
return sectors
.map((x) => SectorOption.values
.firstWhere((option) => option.toString() == x))
.toList();
} else {
return [];
}
} catch (e) {
throw Exception('Error fetching sectors from Firebase: $e');
}
}
} }