Edit ProfilePicture, Bio and Name.
parent
f257bfbabb
commit
f4ed4bd110
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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: {},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue