import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:collection/collection.dart'; import 'package:firebase_auth/firebase_auth.dart'; import '../constants.dart'; import '../enumerations.dart'; import '../models/language.dart'; import '../models/location.dart'; import '../models/user_profile.dart'; class UserService { UserService._(); // Private constructor to prevent instantiation static Future saveUserRegistrationData(UserCredential userCredential, String email, String firstname, String lastname) async { // create full name String fullName = (firstname.isNotEmpty && lastname.isNotEmpty) ? '$firstname $lastname' : (firstname.isNotEmpty ? firstname : (lastname.isNotEmpty ? lastname : '')); // save user info to users document await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(userCredential.user!.uid) .set( { Constants.dbFieldUsersID: userCredential.user!.uid, Constants.dbFieldUsersEmail: email, Constants.dbFieldUsersFirstName: firstname, Constants.dbFieldUsersLastName: lastname, Constants.dbFieldUsersName: fullName, }, ); } static Future> getSkillsFromFirebase( bool skillsSought, String userId) async { // Fetch skills from Firestore DocumentSnapshot userDoc = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(userId) .get(); if (userDoc.exists && userDoc.data() != null) { Map userData = userDoc.data()! as Map; List? skills; if (skillsSought) { skills = userData[Constants.dbFieldUsersSkillsSought]; } else { skills = userData[Constants.dbFieldUsersSkills]; } return convertSkillStringToEnum(skills); } return []; } static List convertSkillStringToEnum(List? skills) { List userSkills = []; if (skills != null && skills.isNotEmpty) { // Convert skills from strings to enum values for (var skill in skills) { try { SkillOption skillOption = SkillOption.values.firstWhere( (x) => x.toString() == 'SkillOption.$skill', ); userSkills.add(skillOption); } catch (e) { // Ignore invalid values continue; } } } return userSkills; } static List convertSectorStringToEnum(List? sectors) { if (sectors != null && sectors.isNotEmpty) { List userSectors = sectors .map((sector) => SectorOption.values.firstWhere((x) => x.toString() == sector)) .toList(); return userSectors; } return []; } static List convertVisionStringToEnum(List? visions) { if (visions != null && visions.isNotEmpty) { List userVisions = visions .map((vision) => VisionOption.values.firstWhere((x) => x.toString() == vision)) .toList(); return userVisions; } return []; } static List convertWorkValuesStringToEnum( List? values) { if (values != null && values.isNotEmpty) { List userWorkValues = values .map((workValue) => WorkValueOption.values .firstWhere((x) => x.toString() == workValue)) .toList(); return userWorkValues; } return []; } static Future saveSkillsToFirebase(List selectedOptions, bool skillsSought, String userId) async { try { // 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; FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(userId) .update({keyToUpdate: skills}); return true; } catch (e) { return false; } } /// Get UserProfile from Document static Future getUserProfileFromDocument( DocumentSnapshot userDoc) async { Map data = userDoc.data() as Map; List skillsOffered = UserService.convertSkillStringToEnum( data[Constants.dbFieldUsersSkills]); List skillsSought = UserService.convertSkillStringToEnum( data[Constants.dbFieldUsersSkillsSought]); List sectors = UserService.convertSectorStringToEnum( data[Constants.dbFieldUsersSectors]); List visions = UserService.convertVisionStringToEnum( data[Constants.dbFieldUsersVisions]); List works = UserService.convertWorkValuesStringToEnum( data[Constants.dbFieldUsersWorkValues]); RiskTolerance risk = RiskTolerance.fromString(data[Constants.dbFieldUsersRiskTolerance]); AvailabilityOption availability = AvailabilityOption.fromString(data[Constants.dbFieldUsersAvailability]); CultureOption culture = CultureOption.fromString(data[Constants.dbFieldUsersCorpCulture]); CommunicationPreference communication = CommunicationPreference.fromString( data[Constants.dbFieldUsersCommunication]); // Retrieve sub collections QuerySnapshot languagesSnapshot = await userDoc.reference .collection(Constants.dbCollectionLanguages) .get(); List languages = languagesSnapshot.docs .map((doc) => Language.fromDocument(doc)) .toList(); QuerySnapshot locationsSnapshot = await userDoc.reference .collection(Constants.dbCollectionLocations) .get(); final mainDoc = locationsSnapshot.docs .firstWhereOrNull((doc) => doc.id == Constants.dbDocMainLocation); final secondaryDoc = locationsSnapshot.docs .firstWhereOrNull((doc) => doc.id == Constants.dbDocSecondLocation); final locations = { Constants.dbDocMainLocation: mainDoc != null ? MyLocation.fromDocument(mainDoc) : null, }; if (secondaryDoc != null) { locations.addAll({ Constants.dbDocSecondLocation: MyLocation.fromDocument(secondaryDoc) }); } // Map locations = { // for (var doc in locationsSnapshot.docs) // doc.id: MyLocation.fromDocument(doc) // }; return UserProfile( id: userDoc.id, uid: data[Constants.dbFieldUsersID] ?? '', email: data[Constants.dbFieldUsersEmail] ?? '', name: data[Constants.dbFieldUsersName] ?? '', firstName: data[Constants.dbFieldUsersFirstName] ?? '', lastName: data[Constants.dbFieldUsersLastName] ?? '', skills: skillsOffered, skillsSought: skillsSought, sectors: sectors, visions: visions, risk: risk, availability: availability, culture: culture, communication: communication, workValues: works, profilePictureUrl: data[Constants.dbFieldUsersProfilePic], bio: data[Constants.dbFieldUsersBio], gender: Gender.values[data[Constants.dbFieldUsersGender] ?? 0], born: data[Constants.dbFieldUsersYearBorn], languages: languages, locations: locations, ); } /// Get UserProfile for given [userId] static Future getUserProfileById(String userId) async { FirebaseFirestore firestore = FirebaseFirestore.instance; DocumentSnapshot userDoc = await firestore .collection(Constants.dbCollectionUsers) .doc(userId) .get(); UserProfile result = await getUserProfileFromDocument(userDoc); return result; } /// Get users stream static Stream>> getUsersStream() { return FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .snapshots() .map((snapshot) { return snapshot.docs.map((doc) { // iterate and return each user final user = doc.data(); return user; }).toList(); }); } /// Get list of matched user ids for a given [userId] static Future> getMatchedUserIds(String userId) async { List matchedUserIds = []; final snapshot = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(userId) .collection(Constants.dbCollectionMatches) .get(); for (var doc in snapshot.docs) { final data = doc.data(); String? otherUserId = data['otherUserId']; if (otherUserId != null && otherUserId.isNotEmpty) { matchedUserIds.add(otherUserId); } } return matchedUserIds; } /// Get matched users data for a given [userId] as stream static Stream>> getMatchedUsersStream( String userId) { return FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(userId) .collection(Constants.dbCollectionMatches) .snapshots() .asyncMap((snapshot) async { final matchedUserIds = snapshot.docs .map((doc) => doc.data()['otherUserId'] as String?) .where((otherUserId) => otherUserId != null && otherUserId.isNotEmpty) .toList(); if (matchedUserIds.isEmpty) { return []; } final matchedUsersSnapshot = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .where(FieldPath.documentId, whereIn: matchedUserIds) .get(); return matchedUsersSnapshot.docs.map((doc) => doc.data()).toList(); }); } /// 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 { DocumentSnapshot matchDoc = await FirebaseFirestore.instance .collection(Constants.dbCollectionUsers) .doc(currentUserId) .collection(Constants.dbCollectionMatches) .doc(userId) .get(); return matchDoc.exists; } catch (e) { 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'); } } }