From 0c0bf431e2d313b3d1c144f17875b9e5e3431a93 Mon Sep 17 00:00:00 2001 From: Rafael <1024481@stud.hs-mannheim.de> Date: Mon, 20 May 2024 01:16:54 +0200 Subject: [PATCH] first registration process --- lib/components/my_drawer.dart | 48 +++++---- lib/forms/profile_category_form.dart | 8 +- lib/forms/skills_form.dart | 117 +++++++--------------- lib/pages/registration_complete_page.dart | 48 +++++++++ lib/pages/user_data_page.dart | 20 ++-- lib/services/auth/auth_gate.dart | 100 ++++++++++++++---- 6 files changed, 205 insertions(+), 136 deletions(-) create mode 100644 lib/pages/registration_complete_page.dart diff --git a/lib/components/my_drawer.dart b/lib/components/my_drawer.dart index c3be1d6..bab8103 100644 --- a/lib/components/my_drawer.dart +++ b/lib/components/my_drawer.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../pages/home_page.dart'; import '../pages/user_data_page.dart'; import '../pages/settings_page.dart'; import '../services/auth/auth_service.dart'; @@ -7,12 +8,6 @@ import 'feedback_dialog.dart'; class MyDrawer extends StatelessWidget { const MyDrawer({super.key}); - void logout() { - // get auth service - final auth = AuthService(); - auth.signOut(); - } - @override Widget build(BuildContext context) { return Drawer( @@ -45,7 +40,13 @@ class MyDrawer extends StatelessWidget { onTap: () { // pop the drawer Navigator.pop(context); - // TODO navigate to Homepage? + // Navigate to HomePage? + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => HomePage(), + ), + ); }, ), ), @@ -77,15 +78,14 @@ class MyDrawer extends StatelessWidget { title: const Text("My Profile"), leading: const Icon(Icons.settings), onTap: () { - // pop the drawer + // pop the drawer and navigate to settings page Navigator.pop(context); - - //navigate to settings page Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const SettingsPage(), - )); + context, + MaterialPageRoute( + builder: (context) => const SettingsPage(), + ), + ); }, ), ), @@ -97,15 +97,16 @@ class MyDrawer extends StatelessWidget { title: const Text("User Data"), leading: const Icon(Icons.supervised_user_circle), onTap: () { - // pop the drawer + // pop the drawer first, then navigate to destination Navigator.pop(context); - - //navigate to settings page Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const UserDataPage(isRegProcess: false,), - )); + context, + MaterialPageRoute( + builder: (context) => const UserDataPage( + isRegProcess: false, + ), + ), + ); }, ), ), @@ -150,7 +151,10 @@ class MyDrawer extends StatelessWidget { child: ListTile( title: const Text("L O G O U T"), leading: const Icon(Icons.logout), - onTap: logout, + onTap: () { + Navigator.of(context).pop(); + AuthService().signOut(); + }, ), ), ], diff --git a/lib/forms/profile_category_form.dart b/lib/forms/profile_category_form.dart index 19d5bc6..c0cfc8a 100644 --- a/lib/forms/profile_category_form.dart +++ b/lib/forms/profile_category_form.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import '../../helper.dart'; class ProfileCategoryForm extends StatefulWidget { final String title; @@ -91,8 +90,7 @@ class ProfileCategoryFormState extends State> { if (!widget.hideSaveButton) ElevatedButton( onPressed: () { - if (!equalContent( - _selectedOptions, widget.preSelectedOptions)) { + //if (!equalContent(_selectedOptions, widget.preSelectedOptions)) { // Only save if options have changed if (_selectedOptions.length >= widget.minSelections) { widget.onSave(_selectedOptions); @@ -102,9 +100,7 @@ class ProfileCategoryFormState extends State> { // Provide feedback that minimum selections not met _showSnackBar('No selection made for ${widget.title}'); } - } else { - print('No changes to save for ${widget.title}.'); - } + // } else { print('No changes to save for ${widget.title}.'); } }, child: Text(widget.saveButtonText ?? 'Save'), ), diff --git a/lib/forms/skills_form.dart b/lib/forms/skills_form.dart index dfb2bb4..f1637e3 100644 --- a/lib/forms/skills_form.dart +++ b/lib/forms/skills_form.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; -import '../../constants.dart'; import '../../enumerations.dart'; import '../../services/auth/auth_service.dart'; +import '../../services/user_service.dart'; +import '../pages/registration_complete_page.dart'; import 'profile_category_form.dart'; class SkillsForm extends StatelessWidget { @@ -12,8 +12,7 @@ class SkillsForm extends StatelessWidget { required this.skillsSought, }); - // get instance of firestore and auth - final FirebaseFirestore _firestore = FirebaseFirestore.instance; + // get instance of auth final AuthService _authService = AuthService(); final bool isRegProcess; @@ -22,7 +21,10 @@ class SkillsForm extends StatelessWidget { @override Widget build(BuildContext context) { return FutureBuilder>( - future: getSkillsFromFirebase(), // Fetch skills from Firebase + 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 @@ -33,8 +35,9 @@ class SkillsForm extends StatelessWidget { List? userSkills = snapshot.data; return ProfileCategoryForm( title: 'Skills', - header: - skillsSought ? 'Skills you are looking for' : 'Your own skills', + header: skillsSought + ? 'Skills you are searching for' + : 'Your own skills', description: skillsSought ? 'Choose up to 3 areas you are looking for in a co-founder' : 'Select up to 3 areas in which you are skilled', @@ -46,21 +49,37 @@ class SkillsForm extends StatelessWidget { userSkills ?? [], // Pass pre-selected skills to the form onSave: (selectedOptions) async { // Handle saving selected options - bool success = await saveSkillsToFirebase( - selectedOptions.cast()); + bool success = await UserService().saveSkillsToFirebase( + selectedOptions.cast(), + skillsSought, + _authService.getCurrentUser()!.uid, + ); // Then navigate to another screen or perform any other action??? if (context.mounted) { if (success) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SkillsForm( - isRegProcess: isRegProcess, - skillsSought: true, + if (isRegProcess && skillsSought) { + Navigator.push( + context, + MaterialPageRoute( + // TODO set following registration page HERE + builder: (context) => const RegistrationCompletePage(), ), - ), - ); + ); + } else if (isRegProcess) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SkillsForm( + isRegProcess: isRegProcess, + skillsSought: true, + ), + ), + ); + } else { + // Navigate back after saving + Navigator.pop(context); + } } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -75,68 +94,4 @@ class SkillsForm extends StatelessWidget { }, ); } - - Future> getSkillsFromFirebase() async { - // Fetch skills from Firestore - String currentUserId = _authService.getCurrentUser()!.uid; - DocumentSnapshot userDoc = await _firestore - .collection(Constants.dbCollectionUsers) - .doc(currentUserId) - .get(); - - if (userDoc.exists && userDoc.data() != null) { - Map userData = - userDoc.data()! as Map; // Explicit cast - - List? skills; - if (skillsSought) { - skills = userData[Constants.dbFieldUsersSkillsSought]; - } else { - skills = userData[ - Constants.dbFieldUsersSkills]; //as List?; // Explicit cast - } - - if (skills != null && skills.isNotEmpty) { - // Convert skills from strings to enum values - List userSkills = skills - .map((skill) => SkillOption.values - .firstWhere((x) => x.toString() == 'SkillOption.$skill')) - .toList(); - return userSkills; - } - } - - return []; - } - - Future saveSkillsToFirebase(List selectedOptions) async { - try { - String currentUserId = _authService.getCurrentUser()!.uid; - - // Convert enum values to strings, removing leading EnumType with split - List skills = selectedOptions - .map((option) => option.toString().split('.').last) - .toList(); - - // Update the corresponding 'skills' field in the user's document - String keyToUpdate = skillsSought - ? Constants.dbFieldUsersSkillsSought - : Constants.dbFieldUsersSkills; - - _firestore - .collection(Constants.dbCollectionUsers) - .doc(currentUserId) - .update({ - keyToUpdate: skills, - }).then((_) { - print('$keyToUpdate saved to Firebase: $skills'); - }).catchError((error) { - print('Failed to save $keyToUpdate: $error'); - }); - - return true; - } catch (e) { - return false; - } - } } diff --git a/lib/pages/registration_complete_page.dart b/lib/pages/registration_complete_page.dart new file mode 100644 index 0000000..009e056 --- /dev/null +++ b/lib/pages/registration_complete_page.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'home_page.dart'; + +class RegistrationCompletePage extends StatelessWidget { + const RegistrationCompletePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, // remove back button + title: const Text('Registration Complete'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.check_circle, + color: Colors.green, + size: 100, + ), + const SizedBox(height: 20), + const Text( + 'Registration completed!', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 80), + const Text( + "You can now use the app.", + style: TextStyle(fontSize: 18), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => HomePage()), + ); + }, + child: const Text("S T A R T"), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/user_data_page.dart b/lib/pages/user_data_page.dart index 0bd516a..8d11343 100644 --- a/lib/pages/user_data_page.dart +++ b/lib/pages/user_data_page.dart @@ -482,15 +482,19 @@ class _UserDataPageState extends State { bool success = await saveUserData(context); if (context.mounted) { if (success) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SkillsForm( - isRegProcess: widget.isRegProcess, - skillsSought: false, + 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( diff --git a/lib/services/auth/auth_gate.dart b/lib/services/auth/auth_gate.dart index 9bfb920..b2e783f 100644 --- a/lib/services/auth/auth_gate.dart +++ b/lib/services/auth/auth_gate.dart @@ -19,18 +19,33 @@ class AuthGate extends StatelessWidget { // check if user is logged in or not if (snapshot.hasData) { return FutureBuilder( - // check database entries, if data is missing - // then complete registration process - // else go to HomePage - future: _checkCollectionsExist(), + // check if user document exists + future: _checkUserExists(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } else if (snapshot.hasData && snapshot.data == true) { - return HomePage(); + return FutureBuilder( + // check database entries, if data is missing + // then complete registration process + // else go to HomePage + future: _checkCollectionsExist(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const CircularProgressIndicator(); + } else if (snapshot.hasData && snapshot.data == true) { + return HomePage(); + } else { + // also in case of (snapshot.hasError) + return const UserDataPage(isRegProcess: true); + } + }, + ); } else { - // also in case (snapshot.hasError) - return const UserDataPage(isRegProcess: true); + // should user entry not exist, recreate it and force logout + _createUserEntry(); + FirebaseAuth.instance.signOut(); + return const LoginOrRegister(); } }, ); @@ -42,21 +57,68 @@ class AuthGate extends StatelessWidget { ); } + Future _checkUserExists() async { + // return true to avoid unneeded read access to database + return true; + /* enable code to check for deleted users + try { + String currentUserId = AuthService().getCurrentUser()!.uid; + final userDoc = await FirebaseFirestore.instance + .collection(Constants.dbCollectionUsers) + .doc(currentUserId) + .get(); + return userDoc.exists; + + } catch (e) { + print(e.toString()); + return false; + } + */ + } + + Future _createUserEntry() async { + try { + var currentUser = AuthService().getCurrentUser(); + String currentUserId = currentUser!.uid; + String email = currentUser.email!; + + await FirebaseFirestore.instance + .collection(Constants.dbCollectionUsers) + .doc(currentUserId) + .set({ + 'uid': currentUserId, + 'email': email, + }); + } catch (e) { + print("Error creating user document: $e"); + } + } + Future _checkCollectionsExist() async { - bool languagesExist = - await _checkUsersCollectionExists(Constants.dbCollectionLanguages); - bool locationsExist = - await _checkUsersCollectionExists(Constants.dbCollectionLocations); - return languagesExist && locationsExist; + try { + bool languagesExist = + await _checkUsersCollectionExists(Constants.dbCollectionLanguages); + bool locationsExist = + await _checkUsersCollectionExists(Constants.dbCollectionLocations); + return languagesExist && locationsExist; + } catch (e) { + print(e.toString()); + return false; + } } Future _checkUsersCollectionExists(String collectionName) async { - String currentUserId = AuthService().getCurrentUser()!.uid; - final collection = FirebaseFirestore.instance - .collection(Constants.dbCollectionUsers) - .doc(currentUserId) - .collection(collectionName); - final snapshot = await collection.limit(1).get(); - return snapshot.docs.isNotEmpty; + try { + String currentUserId = AuthService().getCurrentUser()!.uid; + final collection = FirebaseFirestore.instance + .collection(Constants.dbCollectionUsers) + .doc(currentUserId) + .collection(collectionName); + final snapshot = await collection.limit(1).get(); + return snapshot.docs.isNotEmpty; + } catch (e) { + print(e.toString()); + return false; + } } }