Edit ProfilePicture, Bio and Name.

master
Rafael 2024-05-30 16:37:34 +02:00
parent f257bfbabb
commit f4ed4bd110
8 changed files with 231 additions and 222 deletions

View File

@ -1,19 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MatchedScreen extends StatelessWidget { class MatchedScreen extends StatelessWidget {
final String user1Name; final String currentUserName;
final String user2Name; final String otherUserName;
final String user1ImageUrl; final String currentUserImageUrl;
final String user2ImageUrl; final String otherUserImageUrl;
final VoidCallback onMessageButtonPressed; final VoidCallback onMessageButtonPressed;
final VoidCallback onContinueButtonPressed; final VoidCallback onContinueButtonPressed;
const MatchedScreen({ const MatchedScreen({
super.key, super.key,
required this.user1Name, required this.currentUserName,
required this.user2Name, required this.otherUserName,
required this.user1ImageUrl, required this.currentUserImageUrl,
required this.user2ImageUrl, required this.otherUserImageUrl,
required this.onMessageButtonPressed, required this.onMessageButtonPressed,
required this.onContinueButtonPressed, required this.onContinueButtonPressed,
}); });
@ -28,7 +28,7 @@ class MatchedScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
'You and $user2Name have liked each other!', 'You and $otherUserName have liked each other!',
style: const TextStyle(fontSize: 24), style: const TextStyle(fontSize: 24),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@ -36,27 +36,27 @@ class MatchedScreen extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// TODO imageUrl cant be null or empty with NetworkImage // imageUrl cant be null or empty with NetworkImage
CircleAvatar( CircleAvatar(
backgroundColor: Colors.blueGrey[300], backgroundColor: Colors.blueGrey[300],
backgroundImage: (user1ImageUrl.isEmpty) backgroundImage: (currentUserImageUrl.isEmpty)
? null ? null
: NetworkImage(user1ImageUrl), : NetworkImage(currentUserImageUrl),
radius: 50, radius: 50,
), ),
const SizedBox(width: 24), const SizedBox(width: 24),
CircleAvatar( CircleAvatar(
backgroundColor: Colors.blueGrey[300], backgroundColor: Colors.blueGrey[300],
backgroundImage: (user2ImageUrl.isEmpty) backgroundImage: (otherUserImageUrl.isEmpty)
? null ? null
: NetworkImage(user2ImageUrl), : NetworkImage(otherUserImageUrl),
radius: 50, radius: 50,
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text( Text(
'$user1Name and $user2Name', '$currentUserName and $otherUserName',
style: const TextStyle(fontSize: 20), style: const TextStyle(fontSize: 20),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),

View File

@ -8,9 +8,11 @@ class UserProfile {
final String id; final String id;
final String uid; final String uid;
final String email; final String email;
final String name; String name;
final String firstName; final String firstName;
final String lastName; final String lastName;
String? profilePictureUrl;
String? bio;
final String risk; final String risk;
final List<String> skills; final List<String> skills;
final List<String> skillsSought; final List<String> skillsSought;
@ -24,6 +26,8 @@ class UserProfile {
required this.name, required this.name,
required this.firstName, required this.firstName,
required this.lastName, required this.lastName,
this.profilePictureUrl,
this.bio,
required this.risk, required this.risk,
required this.skills, required this.skills,
required this.skillsSought, required this.skillsSought,
@ -45,6 +49,8 @@ class UserProfile {
skillsSought: skillsSought:
List<String>.from(data[Constants.dbFieldUsersSkillsSought] ?? []), List<String>.from(data[Constants.dbFieldUsersSkillsSought] ?? []),
risk: data[Constants.dbFieldUsersRiskTolerance] ?? '', risk: data[Constants.dbFieldUsersRiskTolerance] ?? '',
profilePictureUrl: data[Constants.dbFieldUsersProfilePic],
bio: data[Constants.dbFieldUsersBio],
languages: [], languages: [],
locations: {}, locations: {},
); );

View File

@ -6,9 +6,10 @@ import 'package:image_picker/image_picker.dart';
import 'dart:io'; import 'dart:io';
import '../constants.dart'; import '../constants.dart';
import '../models/user_profile.dart';
class EditProfilePage extends StatefulWidget { class EditProfilePage extends StatefulWidget {
final Map<String, dynamic> userData; final UserProfile userData;
const EditProfilePage({super.key, required this.userData}); const EditProfilePage({super.key, required this.userData});
@ -26,12 +27,10 @@ class EditProfilePageState extends State<EditProfilePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_nameController = TextEditingController( _nameController = TextEditingController(text: widget.userData.name);
text: widget.userData[Constants.dbFieldUsersName]); _bioController = TextEditingController(text: widget.userData.bio);
_bioController = if (widget.userData.profilePictureUrl != null) {
TextEditingController(text: widget.userData[Constants.dbFieldUsersBio]); profileImageUrl = widget.userData.profilePictureUrl;
if (widget.userData[Constants.dbFieldUsersProfilePic] != null) {
profileImageUrl = widget.userData[Constants.dbFieldUsersProfilePic];
} }
} }
@ -48,11 +47,15 @@ class EditProfilePageState extends State<EditProfilePage> {
void _clearProfileImage() { void _clearProfileImage() {
setState(() { setState(() {
_profileImage = null; _profileImage = null;
widget.userData[Constants.dbFieldUsersProfilePic] = null; profileImageUrl = null;
widget.userData.profilePictureUrl = null;
}); });
} }
Future<void> _saveProfile() async { Future<void> _saveProfile() async {
String nameTrim = _nameController.text.trim();
String bioTrim = _bioController.text.trim();
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
String uid = FirebaseAuth.instance.currentUser!.uid; String uid = FirebaseAuth.instance.currentUser!.uid;
@ -63,31 +66,32 @@ class EditProfilePageState extends State<EditProfilePage> {
.child(uid); // filename = userid .child(uid); // filename = userid
await storageRef.putFile(_profileImage!); await storageRef.putFile(_profileImage!);
profileImageUrl = await storageRef.getDownloadURL(); profileImageUrl = await storageRef.getDownloadURL();
} else {
profileImageUrl = null;
} }
String name = _nameController.text; Map<String, dynamic> resultValues = {
String bio = _bioController.text; Constants.dbFieldUsersName: nameTrim,
Constants.dbFieldUsersBio: bioTrim,
Constants.dbFieldUsersProfilePic: profileImageUrl,
};
await FirebaseFirestore.instance await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers) .collection(Constants.dbCollectionUsers)
.doc(uid) .doc(uid)
.update({ .update(resultValues);
Constants.dbFieldUsersName: name,
Constants.dbFieldUsersBio: bio,
//if (profileImageUrl != null)
Constants.dbFieldUsersProfilePic: profileImageUrl,
});
Navigator.pop(context, { _close(resultValues);
Constants.dbFieldUsersProfilePic: profileImageUrl,
Constants.dbFieldUsersName: name,
Constants.dbFieldUsersBio: bio,
});
} }
} }
/// close this page and return selected values
void _close(Map<String, dynamic> map) {
Navigator.pop(context, {
Constants.dbFieldUsersProfilePic: map[Constants.dbFieldUsersProfilePic],
Constants.dbFieldUsersName: map[Constants.dbFieldUsersName],
Constants.dbFieldUsersBio: map[Constants.dbFieldUsersBio],
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -105,64 +109,9 @@ class EditProfilePageState extends State<EditProfilePage> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
GestureDetector( buildAvatar(context),
onTap: _pickImage, if (_profileImage != null ||
child: Stack( widget.userData.profilePictureUrl != null)
children: [
CircleAvatar(
radius: 50,
backgroundImage: _profileImage != null
? FileImage(_profileImage!) as ImageProvider
: (widget.userData[
Constants.dbFieldUsersProfilePic] !=
null
? NetworkImage(widget.userData[
Constants.dbFieldUsersProfilePic])
as ImageProvider
: null),
child: ClipOval(
child: _profileImage == null &&
widget.userData[Constants
.dbFieldUsersProfilePic] ==
null
? const Icon(Icons.person, size: 50)
: SizedBox(
width: 100,
height: 100,
child: _profileImage != null
? Image.file(
_profileImage!,
fit: BoxFit.cover,
)
: (widget.userData[Constants
.dbFieldUsersProfilePic] !=
null
? Image.network(
widget.userData[Constants
.dbFieldUsersProfilePic],
fit: BoxFit.cover,
)
: null),
),
),
),
Positioned(
bottom: 0,
right: 0,
child: IconButton(
icon: Ink(
decoration: ShapeDecoration(
color: Theme.of(context).colorScheme.primary,
shape: const CircleBorder(),
),
child: const Icon(Icons.edit)),
onPressed: _pickImage,
),
),
],
),
),
if (_profileImage != null)
IconButton( IconButton(
icon: Ink( icon: Ink(
decoration: const ShapeDecoration( decoration: const ShapeDecoration(
@ -184,7 +133,7 @@ class EditProfilePageState extends State<EditProfilePage> {
controller: _nameController, controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'), decoration: const InputDecoration(labelText: 'Name'),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.trim().isEmpty) {
return 'Please enter a name'; return 'Please enter a name';
} }
return null; return null;
@ -195,6 +144,7 @@ class EditProfilePageState extends State<EditProfilePage> {
controller: _bioController, controller: _bioController,
decoration: const InputDecoration(labelText: 'Bio'), decoration: const InputDecoration(labelText: 'Bio'),
maxLines: 3, maxLines: 3,
maxLength: 4096,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton( ElevatedButton(
@ -207,4 +157,56 @@ class EditProfilePageState extends State<EditProfilePage> {
), ),
); );
} }
Widget buildAvatar(BuildContext context) {
return GestureDetector(
onTap: _pickImage,
child: Stack(
children: [
CircleAvatar(
radius: 50,
backgroundImage: _profileImage != null
? FileImage(_profileImage!) as ImageProvider
: (widget.userData.profilePictureUrl != null
? NetworkImage(widget.userData.profilePictureUrl!)
as ImageProvider
: null),
child: ClipOval(
child: _profileImage == null &&
widget.userData.profilePictureUrl == null
? const Icon(Icons.person, size: 50)
: SizedBox(
width: 100,
height: 100,
child: _profileImage != null
? Image.file(
_profileImage!,
fit: BoxFit.cover,
)
: (widget.userData.profilePictureUrl != null
? Image.network(
widget.userData.profilePictureUrl!,
fit: BoxFit.cover,
)
: null),
),
),
),
Positioned(
bottom: 0,
right: 0,
child: IconButton(
icon: Ink(
decoration: ShapeDecoration(
color: Theme.of(context).colorScheme.primary,
shape: const CircleBorder(),
),
child: const Icon(Icons.edit)),
onPressed: _pickImage,
),
),
],
),
);
}
} }

View File

@ -12,6 +12,8 @@ class RegistrationCompletePage extends StatelessWidget {
title: const Text('Registration Complete'), title: const Text('Registration Complete'),
), ),
body: Center( body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -26,9 +28,16 @@ class RegistrationCompletePage extends StatelessWidget {
'Registration completed!', 'Registration completed!',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 40),
const Text(
'Additional information such as a short biography or a '
'profile picture can be entered in the profile options.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 80), const SizedBox(height: 80),
const Text( const Text(
"You can enjoy the app now.", 'You can enjoy the app now.',
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -39,12 +48,13 @@ class RegistrationCompletePage extends StatelessWidget {
MaterialPageRoute(builder: (context) => HomePage()), MaterialPageRoute(builder: (context) => HomePage()),
); );
}, },
child: const Text("S T A R T"), child: const Text('S T A R T'),
), ),
], ],
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -86,10 +86,8 @@ class UserMatchingPageState extends State<UserMatchingPage> {
final languagesSnapshot = await userDoc.reference final languagesSnapshot = await userDoc.reference
.collection(Constants.dbCollectionLanguages) .collection(Constants.dbCollectionLanguages)
.get(); .get();
final locationsSnapshot = await userDoc.reference
.collection(Constants.dbCollectionLocations)
.get();
// get languages
final languages = languagesSnapshot.docs.map((doc) { final languages = languagesSnapshot.docs.map((doc) {
final data = doc.data(); final data = doc.data();
return Language( return Language(
@ -100,6 +98,10 @@ class UserMatchingPageState extends State<UserMatchingPage> {
); );
}).toList(); }).toList();
// get locations
final locationsSnapshot = await userDoc.reference
.collection(Constants.dbCollectionLocations)
.get();
final mainDoc = locationsSnapshot.docs final mainDoc = locationsSnapshot.docs
.firstWhereOrNull((doc) => doc.id == Constants.dbDocMainLocation); .firstWhereOrNull((doc) => doc.id == Constants.dbDocMainLocation);
final secondaryDoc = locationsSnapshot.docs final secondaryDoc = locationsSnapshot.docs
@ -112,21 +114,10 @@ class UserMatchingPageState extends State<UserMatchingPage> {
: null, : null,
}; };
final data = userDoc.data(); // create userProfile including the data above
UserProfile userProfile = UserProfile( UserProfile userProfile = UserProfile.fromDocument(userDoc);
id: userDoc.id, userProfile.locations.addAll(locations);
uid: data[Constants.dbFieldUsersID] ?? '', userProfile.languages.addAll(languages);
email: data[Constants.dbFieldUsersEmail] ?? '',
name: data[Constants.dbFieldUsersName] ?? '',
firstName: data[Constants.dbFieldUsersFirstName] ?? '',
lastName: data[Constants.dbFieldUsersLastName] ?? '',
skills: List<String>.from(data[Constants.dbFieldUsersSkills] ?? []),
skillsSought:
List<String>.from(data[Constants.dbFieldUsersSkillsSought] ?? []),
risk: data[Constants.dbFieldUsersRiskTolerance] ?? '',
languages: languages,
locations: locations,
);
// add profiles accordingly // add profiles accordingly
allUsers.add(userProfile); allUsers.add(userProfile);
@ -194,12 +185,12 @@ class UserMatchingPageState extends State<UserMatchingPage> {
); );
} }
UserProfile getUserProfile(String userId) { UserProfile _getUserProfile(String userId) {
return userProfiles.firstWhere((x) => x.uid == userId); return userProfiles.firstWhere((x) => x.uid == userId);
} }
/// Check whether the swiped user has also swiped to the right /// Check whether the swiped user has also swiped to the right
Future<void> _checkForMatch(swipedUserId) async { Future<void> _checkForMatch(String swipedUserId) async {
String currentUserId = _authService.getCurrentUser()!.uid; String currentUserId = _authService.getCurrentUser()!.uid;
final QuerySnapshot matchSnapshot = await _firestore final QuerySnapshot matchSnapshot = await _firestore
@ -212,6 +203,7 @@ class UserMatchingPageState extends State<UserMatchingPage> {
if (matchSnapshot.docs.isNotEmpty) { if (matchSnapshot.docs.isNotEmpty) {
// save match for both users // save match for both users
// using set method (with UserID) instead of add (with autogenerated ID)
final matchesCurrentUser = _firestore final matchesCurrentUser = _firestore
.collection(Constants.dbCollectionUsers) .collection(Constants.dbCollectionUsers)
.doc(currentUserId) .doc(currentUserId)
@ -221,12 +213,12 @@ class UserMatchingPageState extends State<UserMatchingPage> {
.doc(swipedUserId) .doc(swipedUserId)
.collection(Constants.dbCollectionMatches); .collection(Constants.dbCollectionMatches);
await matchesCurrentUser.add({ await matchesCurrentUser.doc(swipedUserId).set({
'otherUserId': swipedUserId, 'otherUserId': swipedUserId,
'timestamp': FieldValue.serverTimestamp(), 'timestamp': FieldValue.serverTimestamp(),
}); });
await matchesSwipedUser.add({ await matchesSwipedUser.doc(currentUserId).set({
'otherUserId': currentUserId, 'otherUserId': currentUserId,
'timestamp': FieldValue.serverTimestamp(), 'timestamp': FieldValue.serverTimestamp(),
}); });
@ -235,21 +227,24 @@ class UserMatchingPageState extends State<UserMatchingPage> {
// TODO Notify other user? // TODO Notify other user?
// //
showMatchedScreen(currentUserId, swipedUserId); showMatchedScreen(currentUserId, swipedUserId);
// Remove matched user from the list of potential users
potentialUserProfiles.removeWhere((x) => x.uid == swipedUserId);
} }
} }
showMatchedScreen(String currentUserId, String swipedUserId) { showMatchedScreen(String currentUserId, String swipedUserId) {
UserProfile currentUser = getUserProfile(currentUserId); UserProfile currentUser = _getUserProfile(currentUserId);
UserProfile swipedUser = getUserProfile(swipedUserId); UserProfile swipedUser = _getUserProfile(swipedUserId);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => MatchedScreen( builder: (context) => MatchedScreen(
user1Name: swipedUser.name, currentUserName: currentUser.name,
user2Name: currentUser.name, otherUserName: swipedUser.name,
user1ImageUrl: '', // swipedUser.profilePicture, currentUserImageUrl: currentUser.profilePictureUrl ?? '',
user2ImageUrl: '', // currentUser.profilePicture, otherUserImageUrl: swipedUser.profilePictureUrl ?? '',
onMessageButtonPressed: () { onMessageButtonPressed: () {
Navigator.pop(context); // Close the MatchedScreen Navigator.pop(context); // Close the MatchedScreen
Navigator.push( Navigator.push(
@ -270,37 +265,6 @@ class UserMatchingPageState extends State<UserMatchingPage> {
); );
} }
double _shortestDistanceBetweenUsers(
UserProfile currentUser, UserProfile otherUser) {
try {
if (currentUser.locations.isEmpty || otherUser.locations.isEmpty) {
return double.nan;
}
double shortestDistance = double.nan;
// locations currentUser
for (var loc1 in currentUser.locations.values) {
if (loc1 != null && loc1.latitude != null && loc1.longitude != null) {
for (var loc2 in otherUser.locations.values) {
if (loc2 != null &&
loc2.latitude != null &&
loc2.longitude != null) {
double distance = calculateDistance(loc1.latitude!,
loc1.longitude!, loc2.latitude!, loc2.longitude!);
if (shortestDistance.isNaN || distance < shortestDistance) {
shortestDistance = distance;
}
}
}
}
}
return shortestDistance;
} catch (e) {
return double.nan;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (potentialUserProfiles.isEmpty) { if (potentialUserProfiles.isEmpty) {
@ -444,7 +408,7 @@ class UserMatchingPageState extends State<UserMatchingPage> {
Text( Text(
'Second home: ${userProfile.locations[Constants.dbDocSecondLocation]?.locality ?? 'N/A'}'), 'Second home: ${userProfile.locations[Constants.dbDocSecondLocation]?.locality ?? 'N/A'}'),
Text( Text(
'Shortest distance: ${_shortestDistanceBetweenUsers(currentUserProfile!, userProfile).toStringAsFixed(0)} km'), 'Shortest distance: ${shortestDistanceBetweenUsers(currentUserProfile!, userProfile).toStringAsFixed(0)} km'),
], ],
), ),
), ),

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import '../constants.dart'; import '../constants.dart';
import '../models/user_profile.dart';
import '../services/user_service.dart';
import 'edit_profile_page.dart'; import 'edit_profile_page.dart';
class UserProfilePage extends StatefulWidget { class UserProfilePage extends StatefulWidget {
@ -13,25 +15,23 @@ class UserProfilePage extends StatefulWidget {
class _UserProfilePageState extends State<UserProfilePage> { class _UserProfilePageState extends State<UserProfilePage> {
String? profileImageUrl; // Track the profile image URL String? profileImageUrl; // Track the profile image URL
late UserProfile myData;
bool isLoading = true; bool isLoading = true;
late Map<String, dynamic> userData;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadUserData(); // Load user data on initialization // Load user data on initialization
_loadUserData();
} }
Future<void> _loadUserData() async { Future<void> _loadUserData() async {
DocumentSnapshot userDoc = await FirebaseFirestore.instance myData = await UserService.getUserProfileById(
.collection(Constants.dbCollectionUsers) FirebaseAuth.instance.currentUser!.uid);
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();
setState(() { setState(() {
userData = userDoc.data() as Map<String, dynamic>;
// Initialize the profile image URL // Initialize the profile image URL
profileImageUrl = userData[Constants.dbFieldUsersProfilePic]; profileImageUrl = myData.profilePictureUrl;
// Set loading to false once data is loaded // Set loading to false once data is loaded
isLoading = false; isLoading = false;
}); });
@ -41,17 +41,18 @@ class _UserProfilePageState extends State<UserProfilePage> {
final updatedUserData = await Navigator.push( final updatedUserData = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => EditProfilePage(userData: userData), builder: (context) => EditProfilePage(userData: myData),
), ),
); );
if (updatedUserData != null) { if (updatedUserData != null) {
setState(() { setState(() {
// above Type of updatedUserData is dynamic, so check EditProfilePage
profileImageUrl = updatedUserData[Constants.dbFieldUsersProfilePic]; profileImageUrl = updatedUserData[Constants.dbFieldUsersProfilePic];
userData[Constants.dbFieldUsersName] = myData.profilePictureUrl =
updatedUserData[Constants.dbFieldUsersName]; updatedUserData[Constants.dbFieldUsersProfilePic];
userData[Constants.dbFieldUsersBio] = myData.name = updatedUserData[Constants.dbFieldUsersName];
updatedUserData[Constants.dbFieldUsersBio]; myData.bio = updatedUserData[Constants.dbFieldUsersBio];
}); });
} }
} }
@ -68,7 +69,6 @@ class _UserProfilePageState extends State<UserProfilePage> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
//crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Align( Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
@ -88,18 +88,21 @@ class _UserProfilePageState extends State<UserProfilePage> {
: null, : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text(userData[Constants.dbFieldUsersName] ?? 'Name', Text(myData.name, style: const TextStyle(fontSize: 24)),
style: const TextStyle(fontSize: 24)), Text(myData.email, style: const TextStyle(fontSize: 16)),
Text(userData[Constants.dbFieldUsersEmail] ?? 'Email', const SizedBox(height: 32),
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 16),
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Bio'), Text(
Text(userData[Constants.dbFieldUsersBio] ?? 'N/A', 'Short description of yourself',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
Text(myData.bio ?? 'N/A',
style: const TextStyle(fontSize: 16)), style: const TextStyle(fontSize: 16)),
], ],
), ),

View File

@ -42,15 +42,13 @@ class UserService {
.get(); .get();
if (userDoc.exists && userDoc.data() != null) { if (userDoc.exists && userDoc.data() != null) {
Map<String, dynamic> userData = Map<String, dynamic> userData = userDoc.data()! as Map<String, dynamic>;
userDoc.data()! as Map<String, dynamic>; // Explicit cast
List<dynamic>? skills; List<dynamic>? skills;
if (skillsSought) { if (skillsSought) {
skills = userData[Constants.dbFieldUsersSkillsSought]; skills = userData[Constants.dbFieldUsersSkillsSought];
} else { } else {
skills = userData[ skills = userData[Constants.dbFieldUsersSkills];
Constants.dbFieldUsersSkills]; //as List<dynamic>?; // Explicit cast
} }
if (skills != null && skills.isNotEmpty) { if (skills != null && skills.isNotEmpty) {
@ -92,14 +90,6 @@ class UserService {
/// Get UserProfile for given [userId] /// Get UserProfile for given [userId]
static Future<UserProfile> getUserProfileById(String userId) async { static Future<UserProfile> getUserProfileById(String userId) async {
DocumentSnapshot doc = await FirebaseFirestore.instance
.collection(Constants.dbCollectionUsers)
.doc(userId)
.get();
return UserProfile.fromDocument(doc);
}
static Future<UserProfile> getDataById(String userId) async {
FirebaseFirestore firestore = FirebaseFirestore.instance; FirebaseFirestore firestore = FirebaseFirestore.instance;
DocumentSnapshot userDoc = await firestore DocumentSnapshot userDoc = await firestore

View File

@ -1,5 +1,7 @@
import 'dart:math'; import 'dart:math';
import '../models/user_profile.dart';
/// ///
/// Convert decimal coordinate to degrees minutes seconds (DMS). /// Convert decimal coordinate to degrees minutes seconds (DMS).
/// ///
@ -45,3 +47,35 @@ double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
double _degreesToRadians(double degrees) { double _degreesToRadians(double degrees) {
return degrees * pi / 180; return degrees * pi / 180;
} }
///
/// Shortest distance between two users locations
///
double shortestDistanceBetweenUsers(
UserProfile currentUser, UserProfile otherUser) {
try {
if (currentUser.locations.isEmpty || otherUser.locations.isEmpty) {
return double.nan;
}
double shortestDistance = double.nan;
// locations currentUser
for (var loc1 in currentUser.locations.values) {
if (loc1 != null && loc1.latitude != null && loc1.longitude != null) {
for (var loc2 in otherUser.locations.values) {
if (loc2 != null && loc2.latitude != null && loc2.longitude != null) {
double distance = calculateDistance(loc1.latitude!, loc1.longitude!,
loc2.latitude!, loc2.longitude!);
if (shortestDistance.isNaN || distance < shortestDistance) {
shortestDistance = distance;
}
}
}
}
}
return shortestDistance;
} catch (e) {
return double.nan;
}
}