Enhanced UserProfilePage to showcase either own or another user's profile

master
Rafael 2024-06-09 03:01:52 +02:00
parent 98920a4e61
commit 8120ebda3d
7 changed files with 189 additions and 143 deletions

View File

@ -130,13 +130,14 @@ class UserTileChats extends StatelessWidget {
size: 16, size: 16,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Flexible( if (msgDateString.isNotEmpty)
child: Text( Flexible(
msgDateString, child: Text(
overflow: TextOverflow.ellipsis, msgDateString,
maxLines: 1, overflow: TextOverflow.ellipsis,
maxLines: 1,
),
), ),
),
], ],
), ),
], ],

View File

@ -3,17 +3,20 @@ import 'package:flutter/material.dart';
import '../components/chat_bubble.dart'; import '../components/chat_bubble.dart';
import '../components/my_textfield.dart'; import '../components/my_textfield.dart';
import '../constants.dart';
import '../services/auth/auth_service.dart'; import '../services/auth/auth_service.dart';
import '../services/chat/chat_service.dart'; import '../services/chat/chat_service.dart';
class ChatPage extends StatefulWidget { class ChatPage extends StatefulWidget {
final String receiverEmail; final String receiverEmail;
final String receiverID; final String receiverID;
final String chatTitle;
const ChatPage({ const ChatPage({
super.key, super.key,
required this.receiverEmail, required this.receiverEmail,
required this.receiverID, required this.receiverID,
required this.chatTitle,
}); });
@override @override
@ -88,7 +91,7 @@ class _ChatPageState extends State<ChatPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(widget.receiverEmail)), appBar: AppBar(title: Text(widget.chatTitle)),
body: Column( body: Column(
children: [ children: [
// display all messages // display all messages
@ -132,12 +135,16 @@ class _ChatPageState extends State<ChatPage> {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>; Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
// align message to the right if sender is current user, otherwise left // align message to the right if sender is current user, otherwise left
bool isCurrentUser = data['senderID'] == _authService.getCurrentUser()!.uid; bool isCurrentUser = data[Constants.dbFieldMessageSenderId] ==
_authService.getCurrentUser()!.uid;
var alignment = var alignment =
isCurrentUser ? Alignment.centerRight : Alignment.centerLeft; isCurrentUser ? Alignment.centerRight : Alignment.centerLeft;
List<String> msgDate = List<String> msgDate =
(data['timestamp'] as Timestamp).toDate().toIso8601String().split("T"); (data[Constants.dbFieldMessageTimestamp] as Timestamp)
.toDate()
.toIso8601String()
.split("T");
return Container( return Container(
alignment: alignment, alignment: alignment,
@ -153,7 +160,7 @@ class _ChatPageState extends State<ChatPage> {
), ),
), ),
ChatBubble( ChatBubble(
message: data['message'], message: data[Constants.dbFieldMessageText],
isCurrentUser: isCurrentUser, isCurrentUser: isCurrentUser,
), ),
], ],

View File

@ -78,6 +78,7 @@ class ConversationsPage extends StatelessWidget {
builder: (context) => ChatPage( builder: (context) => ChatPage(
receiverEmail: userData[Constants.dbFieldUsersEmail], receiverEmail: userData[Constants.dbFieldUsersEmail],
receiverID: userData[Constants.dbFieldUsersID], receiverID: userData[Constants.dbFieldUsersID],
chatTitle: userData[Constants.dbFieldUsersName],
), ),
), ),
); );

View File

@ -7,6 +7,7 @@ import '../enumerations.dart';
import '../models/swipe.dart'; import '../models/swipe.dart';
import '../services/auth/auth_service.dart'; import '../services/auth/auth_service.dart';
import '../utils/helper.dart'; import '../utils/helper.dart';
import 'user_profile_page.dart';
class LikedUsersPage extends StatefulWidget { class LikedUsersPage extends StatefulWidget {
const LikedUsersPage({super.key}); const LikedUsersPage({super.key});
@ -143,32 +144,6 @@ class LikedUsersPageState extends State<LikedUsersPage> {
return confirm; return confirm;
} }
void _showUserInfo(DocumentSnapshot user) {
Map<String, dynamic> userMap = user.data() as Map<String, dynamic>;
bool hasName = userMap.containsKey(Constants.dbFieldUsersName);
bool hasBio = userMap.containsKey(Constants.dbFieldUsersBio);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: hasName
? Text(user[Constants.dbFieldUsersName])
: const Text('Name: n/a'),
content: hasBio
? Text(user[Constants.dbFieldUsersBio])
: const Text('Bio: n/a'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
],
);
},
);
}
List<MapEntry<Swipe, DocumentSnapshot>> _getSortedLikedUsersWithSwipes() { List<MapEntry<Swipe, DocumentSnapshot>> _getSortedLikedUsersWithSwipes() {
List<MapEntry<Swipe, DocumentSnapshot>> likedOnlyUsers = []; List<MapEntry<Swipe, DocumentSnapshot>> likedOnlyUsers = [];
List<MapEntry<Swipe, DocumentSnapshot>> matchedUsers = []; List<MapEntry<Swipe, DocumentSnapshot>> matchedUsers = [];
@ -376,7 +351,16 @@ class LikedUsersPageState extends State<LikedUsersPage> {
} }
}, //_unlikeUser(user.id), }, //_unlikeUser(user.id),
onShowMatchMessage: _showMatchMessage, onShowMatchMessage: _showMatchMessage,
onViewInfo: () => _showUserInfo(user), onViewInfo: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => UserProfilePage(
userId: user.id,
),
),
);
},
); );
}, },
), ),

View File

@ -257,6 +257,7 @@ class UserMatchingPageState extends State<UserMatchingPage> {
builder: (context) => ChatPage( builder: (context) => ChatPage(
receiverEmail: swipedUser.email, receiverEmail: swipedUser.email,
receiverID: swipedUser.uid, receiverID: swipedUser.uid,
chatTitle: swipedUser.name,
), ),
), ),
); );
@ -390,9 +391,6 @@ class UserMatchingPageState extends State<UserMatchingPage> {
Widget _buildUserCard(UserProfile userProfile) { Widget _buildUserCard(UserProfile userProfile) {
String? profileImageUrl = userProfile.profilePictureUrl; String? profileImageUrl = userProfile.profilePictureUrl;
int age = calcAge(userProfile.born);
String ageInfo = age > 0 ? ' ($age)' : '';
// Sort the languages according to the given order below // Sort the languages according to the given order below
List<Language> sortedLanguages = List.from(userProfile.languages); List<Language> sortedLanguages = List.from(userProfile.languages);
sortedLanguages.sort((a, b) { sortedLanguages.sort((a, b) {
@ -437,7 +435,8 @@ class UserMatchingPageState extends State<UserMatchingPage> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Center( Center(
child: Text('${userProfile.name}$ageInfo', child: Text(
'${userProfile.name} ${ageInfo(userProfile.born)}'.trim(),
style: const TextStyle(fontSize: 24)), style: const TextStyle(fontSize: 24)),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),

View File

@ -15,7 +15,9 @@ import 'user_data_page.dart';
import 'user_vision_page.dart'; import 'user_vision_page.dart';
class UserProfilePage extends StatefulWidget { class UserProfilePage extends StatefulWidget {
const UserProfilePage({super.key}); const UserProfilePage({super.key, this.userId});
final String? userId;
@override @override
State<UserProfilePage> createState() => _UserProfilePageState(); State<UserProfilePage> createState() => _UserProfilePageState();
@ -25,17 +27,23 @@ class _UserProfilePageState extends State<UserProfilePage> {
String? profileImageUrl; // Track the profile image URL String? profileImageUrl; // Track the profile image URL
late UserProfile myData; late UserProfile myData;
bool isLoading = true; bool isLoading = true;
late bool isOwner;
late String _userId;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Determine the userId to use, then check if user is the current user
_userId = widget.userId ?? FirebaseAuth.instance.currentUser!.uid;
isOwner = (_userId == FirebaseAuth.instance.currentUser!.uid);
// Load user data on initialization // Load user data on initialization
_loadUserData(); _loadUserData();
} }
Future<void> _loadUserData() async { Future<void> _loadUserData() async {
myData = await UserService.getUserProfileById( myData = await UserService.getUserProfileById(_userId);
FirebaseAuth.instance.currentUser!.uid);
setState(() { setState(() {
// Initialize the profile image URL // Initialize the profile image URL
@ -193,7 +201,7 @@ class _UserProfilePageState extends State<UserProfilePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('My Profile Information'), title: Text(isOwner ? 'My Profile Information' : 'Profile Information'),
), ),
body: isLoading body: isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
@ -204,28 +212,34 @@ class _UserProfilePageState extends State<UserProfilePage> {
children: [ children: [
_buildAvatar(context), _buildAvatar(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Divider(color: Theme.of(context).colorScheme.primary), if (isOwner)
const SizedBox(height: 16), Divider(color: Theme.of(context).colorScheme.primary),
if (isOwner) const SizedBox(height: 16),
_buildLocation(context), _buildLocation(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Divider(color: Theme.of(context).colorScheme.primary), if (isOwner)
const SizedBox(height: 16), Divider(color: Theme.of(context).colorScheme.primary),
if (isOwner) const SizedBox(height: 16),
_buildSkills(context), _buildSkills(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Divider(color: Theme.of(context).colorScheme.primary), if (isOwner)
const SizedBox(height: 16), Divider(color: Theme.of(context).colorScheme.primary),
if (isOwner) const SizedBox(height: 16),
_buildVision(context), _buildVision(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Divider(color: Theme.of(context).colorScheme.primary), if (isOwner)
const SizedBox(height: 16), Divider(color: Theme.of(context).colorScheme.primary),
if (isOwner) const SizedBox(height: 16),
_buildWorkCulture(context), _buildWorkCulture(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Divider(color: Theme.of(context).colorScheme.primary), if (isOwner)
const SizedBox(height: 16), Divider(color: Theme.of(context).colorScheme.primary),
if (isOwner) const SizedBox(height: 16),
_buildRisks(context), _buildRisks(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Divider(color: Theme.of(context).colorScheme.primary), if (isOwner)
const SizedBox(height: 16), Divider(color: Theme.of(context).colorScheme.primary),
if (isOwner) const SizedBox(height: 16),
], ],
), ),
), ),
@ -259,17 +273,18 @@ class _UserProfilePageState extends State<UserProfilePage> {
], ],
), ),
), ),
Padding( if (isOwner)
padding: const EdgeInsets.only(left: 8.0), Padding(
child: Align( padding: const EdgeInsets.only(left: 8.0),
alignment: Alignment.topRight, child: Align(
child: OutlinedButton.icon( alignment: Alignment.topRight,
label: const Text('Edit'), child: OutlinedButton.icon(
icon: const Icon(Icons.edit), label: const Text('Edit'),
onPressed: editUserCommunicationInfo, icon: const Icon(Icons.edit),
onPressed: editUserCommunicationInfo,
),
), ),
), ),
),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -326,17 +341,18 @@ class _UserProfilePageState extends State<UserProfilePage> {
], ],
), ),
), ),
Padding( if (isOwner)
padding: const EdgeInsets.only(left: 8.0), Padding(
child: Align( padding: const EdgeInsets.only(left: 8.0),
alignment: Alignment.topRight, child: Align(
child: OutlinedButton.icon( alignment: Alignment.topRight,
label: const Text('Edit'), child: OutlinedButton.icon(
icon: const Icon(Icons.edit), label: const Text('Edit'),
onPressed: editUserWorkCultureInfo, icon: const Icon(Icons.edit),
onPressed: editUserWorkCultureInfo,
),
), ),
), ),
),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -396,14 +412,15 @@ class _UserProfilePageState extends State<UserProfilePage> {
], ],
), ),
), ),
Align( if (isOwner)
alignment: Alignment.topRight, Align(
child: OutlinedButton.icon( alignment: Alignment.topRight,
label: const Text('Edit'), child: OutlinedButton.icon(
icon: const Icon(Icons.edit), label: const Text('Edit'),
onPressed: editUserVisionInfo, icon: const Icon(Icons.edit),
onPressed: editUserVisionInfo,
),
), ),
),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -463,14 +480,15 @@ class _UserProfilePageState extends State<UserProfilePage> {
], ],
), ),
), ),
Align( if (isOwner)
alignment: Alignment.topRight, Align(
child: OutlinedButton.icon( alignment: Alignment.topRight,
label: const Text('Edit'), child: OutlinedButton.icon(
icon: const Icon(Icons.edit), label: const Text('Edit'),
onPressed: editUserSkillsInfo, icon: const Icon(Icons.edit),
onPressed: editUserSkillsInfo,
),
), ),
),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -495,14 +513,15 @@ class _UserProfilePageState extends State<UserProfilePage> {
], ],
), ),
), ),
Align( if (isOwner)
alignment: Alignment.topRight, Align(
child: OutlinedButton.icon( alignment: Alignment.topRight,
label: const Text('Edit'), child: OutlinedButton.icon(
icon: const Icon(Icons.edit), label: const Text('Edit'),
onPressed: editUserSkillsSoughtInfo, icon: const Icon(Icons.edit),
onPressed: editUserSkillsSoughtInfo,
),
), ),
),
], ],
), ),
], ],
@ -521,44 +540,47 @@ class _UserProfilePageState extends State<UserProfilePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( if (isOwner)
mainAxisAlignment: MainAxisAlignment.spaceBetween, Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Text( children: [
'Age', Text(
style: TextStyle( 'Age',
color: Theme.of(context).colorScheme.primary, style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
), ),
), Text(
Text( (age > 0
(age > 0 ? '$age years old, born ${myData.born}'
? '$age years old, born ${myData.born}' : 'n/a'),
: 'n/a'), style: const TextStyle(fontSize: 16)),
style: const TextStyle(fontSize: 16)), ],
],
),
Align(
alignment: Alignment.bottomRight,
child: OutlinedButton.icon(
label: const Text('Edit'),
icon: const Icon(Icons.edit),
onPressed: editUserDataInfo,
), ),
), Align(
], alignment: Alignment.bottomRight,
), child: OutlinedButton.icon(
const SizedBox(height: 16), label: const Text('Edit'),
Text( icon: const Icon(Icons.edit),
'Gender', onPressed: editUserDataInfo,
style: TextStyle( ),
color: Theme.of(context).colorScheme.primary, ),
],
), ),
), if (isOwner) const SizedBox(height: 16),
Text(getDisplayText(myData.gender), if (isOwner)
style: const TextStyle(fontSize: 16)), Text(
'Gender',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
if (isOwner)
Text(getDisplayText(myData.gender),
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Spoken languages', 'Spoken languages',
@ -597,16 +619,27 @@ class _UserProfilePageState extends State<UserProfilePage> {
} }
Widget _buildAvatar(BuildContext context) { Widget _buildAvatar(BuildContext context) {
Widget genderIcon = const Icon(null);
if (myData.gender == Gender.male) {
genderIcon = const Padding(
padding: EdgeInsets.only(left: 4.0),
child: Icon(Icons.male, color: Colors.blue),
);
} else if (myData.gender == Gender.female) {
genderIcon = const Icon(Icons.female, color: Colors.pink);
}
return Column( return Column(
children: [ children: [
Align( if (isOwner)
alignment: Alignment.bottomRight, Align(
child: OutlinedButton.icon( alignment: Alignment.bottomRight,
label: const Text('Edit'), child: OutlinedButton.icon(
icon: const Icon(Icons.edit), label: const Text('Edit'),
onPressed: editNameInfo, icon: const Icon(Icons.edit),
onPressed: editNameInfo,
),
), ),
),
CircleAvatar( CircleAvatar(
radius: 80, radius: 80,
backgroundImage: backgroundImage:
@ -618,8 +651,19 @@ class _UserProfilePageState extends State<UserProfilePage> {
: null, : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text(myData.name, style: const TextStyle(fontSize: 24)), Row(
Text(myData.email, style: const TextStyle(fontSize: 16)), mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
isOwner
? myData.name
: '${myData.name} ${ageInfo(myData.born)}'.trim(),
style: const TextStyle(fontSize: 24),
),
genderIcon,
],
),
if (isOwner) Text(myData.email, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 32), const SizedBox(height: 32),
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
@ -627,7 +671,7 @@ class _UserProfilePageState extends State<UserProfilePage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Short description of yourself', isOwner ? 'Short description of yourself' : 'Short description',
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),

View File

@ -10,6 +10,14 @@ int calcAge(int? birthYear) {
return 0; return 0;
} }
/// Returns the approximate age in parentheses,
/// or an empty string if [birthYear] is the current year or null.
String ageInfo(int? birthYear) {
int age = calcAge(birthYear);
String ageInfo = age > 0 ? '($age)' : '';
return ageInfo;
}
/// ///
/// Convert decimal coordinate to degrees minutes seconds (DMS). /// Convert decimal coordinate to degrees minutes seconds (DMS).
/// ///
@ -45,8 +53,10 @@ double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
final dLon = _degreesToRadians(lon2 - lon1); final dLon = _degreesToRadians(lon2 - lon1);
final a = sin(dLat / 2) * sin(dLat / 2) + final a = sin(dLat / 2) * sin(dLat / 2) +
cos(_degreesToRadians(lat1)) * cos(_degreesToRadians(lat2)) * cos(_degreesToRadians(lat1)) *
sin(dLon / 2) * sin(dLon / 2); cos(_degreesToRadians(lat2)) *
sin(dLon / 2) *
sin(dLon / 2);
final c = 2 * atan2(sqrt(a), sqrt(1 - a)); final c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c; return R * c;