From 87beb2dace83af749b7235aeda26313418d6441a Mon Sep 17 00:00:00 2001 From: Rafael <1024481@stud.hs-mannheim.de> Date: Thu, 20 Jun 2024 22:47:54 +0200 Subject: [PATCH] Added Sectors of Interest and Save Button to AppBars. --- lib/constants.dart | 1 + lib/enumerations.dart | 95 ++++++++++++++++++ lib/forms/corporate_culture_form.dart | 7 ++ lib/forms/profile_category_form.dart | 29 ++++-- lib/forms/risks_form.dart | 7 ++ lib/forms/sectors_form.dart | 89 +++++++++++++++++ lib/forms/skills_form.dart | 9 +- lib/pages/edit_profile_page.dart | 6 ++ lib/pages/user_vision_page.dart | 7 ++ lib/pages/welcome_page.dart | 132 +++++++++++++------------- lib/services/user_service.dart | 40 +++++++- 11 files changed, 340 insertions(+), 82 deletions(-) create mode 100644 lib/forms/sectors_form.dart diff --git a/lib/constants.dart b/lib/constants.dart index 98ebd62..75f200c 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -54,6 +54,7 @@ class Constants { static const String dbFieldUsersCorpCulture = 'corp_culture'; static const String dbFieldUsersCommunication = 'communication'; static const String dbFieldUsersRiskTolerance = 'risk_tolerance'; + static const String dbFieldUsersSectors = 'sectors_of_interest'; static const String dbStoragePathProfiles = 'profile_images'; diff --git a/lib/enumerations.dart b/lib/enumerations.dart index 738b6ce..4d0164b 100644 --- a/lib/enumerations.dart +++ b/lib/enumerations.dart @@ -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'; + } + } +} diff --git a/lib/forms/corporate_culture_form.dart b/lib/forms/corporate_culture_form.dart index 0d4e3e8..856140b 100644 --- a/lib/forms/corporate_culture_form.dart +++ b/lib/forms/corporate_culture_form.dart @@ -151,6 +151,13 @@ class CultureValuesFormPageState extends State { return Scaffold( appBar: AppBar( title: const Text('Personal preferences'), + actions: [ + if (widget.isEditMode && !widget.isRegProcess) + IconButton( + onPressed: handleSubmit, + icon: const Icon(Icons.save), + ) + ], ), body: Padding( padding: const EdgeInsets.all(16.0), diff --git a/lib/forms/profile_category_form.dart b/lib/forms/profile_category_form.dart index 9379469..15fed22 100644 --- a/lib/forms/profile_category_form.dart +++ b/lib/forms/profile_category_form.dart @@ -25,7 +25,7 @@ class ProfileCategoryForm extends StatefulWidget { required this.onSave, required this.preSelectedOptions, this.saveButtonText, - this.hideSaveButton = false, // Initialize hideSaveButton parameter + this.hideSaveButton = false, }); @override @@ -47,6 +47,13 @@ class ProfileCategoryFormState extends State> { return Scaffold( appBar: AppBar( title: Text(widget.title), + actions: [ + if (!widget.hideSaveButton) + IconButton( + onPressed: _saveButtonClicked, + icon: const Icon(Icons.save), + ) + ], ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), @@ -70,9 +77,9 @@ class ProfileCategoryFormState extends State> { if (_selectedOptions.length < widget.maxSelections) { _selectedOptions.add(option); } else { - // Provide feedback that maximum selections reached _showSnackBar( - 'Maximum selections reached for ${widget.title}'); + 'Maximum selections reached for ${widget.title}', + ); } } else { _selectedOptions.remove(option); @@ -85,13 +92,7 @@ class ProfileCategoryFormState extends State> { const SizedBox(height: 16.0), if (!widget.hideSaveButton) ElevatedButton( - onPressed: () { - if (_selectedOptions.length >= widget.minSelections) { - widget.onSave(_selectedOptions); - } else { - _showSnackBar('No selection made for ${widget.title}'); - } - }, + onPressed: _saveButtonClicked, child: Text(widget.saveButtonText ?? 'Save'), ), ], @@ -100,6 +101,14 @@ class ProfileCategoryFormState extends State> { ); } + void _saveButtonClicked() { + if (_selectedOptions.length >= widget.minSelections) { + widget.onSave(_selectedOptions); + } else { + _showSnackBar('No selection made for ${widget.title}'); + } + } + void _showSnackBar(String message) { if (!_isSnackBarVisible) { _isSnackBarVisible = true; diff --git a/lib/forms/risks_form.dart b/lib/forms/risks_form.dart index e3839df..9ce3eed 100644 --- a/lib/forms/risks_form.dart +++ b/lib/forms/risks_form.dart @@ -129,6 +129,13 @@ class RisksFormPageState extends State { return Scaffold( appBar: AppBar( title: const Text('Personal preferences'), + actions: [ + if (widget.isEditMode && !widget.isRegProcess) + IconButton( + onPressed: handleSubmit, + icon: const Icon(Icons.save), + ) + ], ), body: Padding( padding: const EdgeInsets.all(16.0), diff --git a/lib/forms/sectors_form.dart b/lib/forms/sectors_form.dart new file mode 100644 index 0000000..f8fd896 --- /dev/null +++ b/lib/forms/sectors_form.dart @@ -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>( + 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? 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(), + _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.'), + ), + ); + } + } + }, + ); + } + }, + ); + } +} diff --git a/lib/forms/skills_form.dart b/lib/forms/skills_form.dart index cbd4dad..e8d8714 100644 --- a/lib/forms/skills_form.dart +++ b/lib/forms/skills_form.dart @@ -3,8 +3,8 @@ 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'; +import 'sectors_form.dart'; class SkillsForm extends StatelessWidget { SkillsForm({ @@ -14,7 +14,6 @@ class SkillsForm extends StatelessWidget { required this.isEditMode, }); - // get instance of auth final AuthService _authService = AuthService(); final bool isRegProcess; @@ -27,7 +26,7 @@ class SkillsForm extends StatelessWidget { future: UserService.getSkillsFromFirebase( skillsSought, _authService.getCurrentUser()!.uid, - ), // Fetch skills from Firebase + ), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { // Show loading indicator while fetching data @@ -58,7 +57,7 @@ class SkillsForm extends StatelessWidget { _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 (success) { if (isRegProcess && skillsSought) { @@ -66,7 +65,7 @@ class SkillsForm extends StatelessWidget { context, MaterialPageRoute( // set following registration page HERE - builder: (context) => UserVisionPage( + builder: (context) => SectorsForm( isRegProcess: isRegProcess, isEditMode: false, ), diff --git a/lib/pages/edit_profile_page.dart b/lib/pages/edit_profile_page.dart index f4d5b31..e44dc4c 100644 --- a/lib/pages/edit_profile_page.dart +++ b/lib/pages/edit_profile_page.dart @@ -160,6 +160,12 @@ class EditProfilePageState extends State { return Scaffold( appBar: AppBar( title: const Text('Edit Profile'), + actions: [ + IconButton( + onPressed: _saveProfile, + icon: const Icon(Icons.save), + ) + ], ), body: Padding( padding: const EdgeInsets.all(16.0), diff --git a/lib/pages/user_vision_page.dart b/lib/pages/user_vision_page.dart index e70b92e..f22241d 100644 --- a/lib/pages/user_vision_page.dart +++ b/lib/pages/user_vision_page.dart @@ -149,6 +149,13 @@ class UserVisionPageState extends State { return Scaffold( appBar: AppBar( title: const Text('Personal preferences'), + actions: [ + if (widget.isEditMode && !widget.isRegProcess) + IconButton( + onPressed: handleSubmit, + icon: const Icon(Icons.save), + ) + ], ), body: Padding( padding: const EdgeInsets.all(16.0), diff --git a/lib/pages/welcome_page.dart b/lib/pages/welcome_page.dart index 14e5a49..b7553f6 100644 --- a/lib/pages/welcome_page.dart +++ b/lib/pages/welcome_page.dart @@ -10,73 +10,73 @@ class WelcomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Start now'), - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const TextBold( - text: 'Welcome to ${Constants.appTitle}', - 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, - ), - ], + appBar: AppBar(title: const Text('Welcome to ${Constants.appTitle}'),), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const TextBold( + text: 'Connect with Potential Co-Founders Here', + 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, + ), + ], + ), + ), + ], + ), ), ), ), diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart index c76eebc..2ae9b8c 100644 --- a/lib/services/user_service.dart +++ b/lib/services/user_service.dart @@ -272,7 +272,7 @@ class UserService { /// Checks if a match between currentUser and [userId] exists. /// Returns false in case of an error. static Future hasMatch(String currentUserId, String userId) async { - try { + try { DocumentSnapshot matchDoc = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(currentUserId) @@ -284,4 +284,42 @@ class UserService { return false; } } + + static Future saveSectorsToFirebase( + List sectors, String userId) async { + try { + List 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> 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; + List 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'); + } + } }