diff --git a/lib/components/user_tile_chats.dart b/lib/components/user_tile_chats.dart index 0a53e99..a076ed7 100644 --- a/lib/components/user_tile_chats.dart +++ b/lib/components/user_tile_chats.dart @@ -130,13 +130,14 @@ class UserTileChats extends StatelessWidget { size: 16, ), const SizedBox(width: 4), - Flexible( - child: Text( - msgDateString, - overflow: TextOverflow.ellipsis, - maxLines: 1, + if (msgDateString.isNotEmpty) + Flexible( + child: Text( + msgDateString, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), ), - ), ], ), ], diff --git a/lib/pages/chat_page.dart b/lib/pages/chat_page.dart index 1c00776..4ea2d20 100644 --- a/lib/pages/chat_page.dart +++ b/lib/pages/chat_page.dart @@ -3,17 +3,20 @@ import 'package:flutter/material.dart'; import '../components/chat_bubble.dart'; import '../components/my_textfield.dart'; +import '../constants.dart'; import '../services/auth/auth_service.dart'; import '../services/chat/chat_service.dart'; class ChatPage extends StatefulWidget { final String receiverEmail; final String receiverID; + final String chatTitle; const ChatPage({ super.key, required this.receiverEmail, required this.receiverID, + required this.chatTitle, }); @override @@ -88,7 +91,7 @@ class _ChatPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(widget.receiverEmail)), + appBar: AppBar(title: Text(widget.chatTitle)), body: Column( children: [ // display all messages @@ -132,12 +135,16 @@ class _ChatPageState extends State { Map data = doc.data() as Map; // 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 = isCurrentUser ? Alignment.centerRight : Alignment.centerLeft; List msgDate = - (data['timestamp'] as Timestamp).toDate().toIso8601String().split("T"); + (data[Constants.dbFieldMessageTimestamp] as Timestamp) + .toDate() + .toIso8601String() + .split("T"); return Container( alignment: alignment, @@ -153,7 +160,7 @@ class _ChatPageState extends State { ), ), ChatBubble( - message: data['message'], + message: data[Constants.dbFieldMessageText], isCurrentUser: isCurrentUser, ), ], diff --git a/lib/pages/conversations_page.dart b/lib/pages/conversations_page.dart index dea45dd..36fe2ed 100644 --- a/lib/pages/conversations_page.dart +++ b/lib/pages/conversations_page.dart @@ -78,6 +78,7 @@ class ConversationsPage extends StatelessWidget { builder: (context) => ChatPage( receiverEmail: userData[Constants.dbFieldUsersEmail], receiverID: userData[Constants.dbFieldUsersID], + chatTitle: userData[Constants.dbFieldUsersName], ), ), ); diff --git a/lib/pages/liked_users_page.dart b/lib/pages/liked_users_page.dart index a40bada..430a0de 100644 --- a/lib/pages/liked_users_page.dart +++ b/lib/pages/liked_users_page.dart @@ -7,6 +7,7 @@ import '../enumerations.dart'; import '../models/swipe.dart'; import '../services/auth/auth_service.dart'; import '../utils/helper.dart'; +import 'user_profile_page.dart'; class LikedUsersPage extends StatefulWidget { const LikedUsersPage({super.key}); @@ -143,32 +144,6 @@ class LikedUsersPageState extends State { return confirm; } - void _showUserInfo(DocumentSnapshot user) { - Map userMap = user.data() as Map; - 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> _getSortedLikedUsersWithSwipes() { List> likedOnlyUsers = []; List> matchedUsers = []; @@ -376,7 +351,16 @@ class LikedUsersPageState extends State { } }, //_unlikeUser(user.id), onShowMatchMessage: _showMatchMessage, - onViewInfo: () => _showUserInfo(user), + onViewInfo: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => UserProfilePage( + userId: user.id, + ), + ), + ); + }, ); }, ), diff --git a/lib/pages/user_matching_page.dart b/lib/pages/user_matching_page.dart index aa271b4..9017d42 100644 --- a/lib/pages/user_matching_page.dart +++ b/lib/pages/user_matching_page.dart @@ -257,6 +257,7 @@ class UserMatchingPageState extends State { builder: (context) => ChatPage( receiverEmail: swipedUser.email, receiverID: swipedUser.uid, + chatTitle: swipedUser.name, ), ), ); @@ -390,9 +391,6 @@ class UserMatchingPageState extends State { Widget _buildUserCard(UserProfile userProfile) { String? profileImageUrl = userProfile.profilePictureUrl; - int age = calcAge(userProfile.born); - String ageInfo = age > 0 ? ' ($age)' : ''; - // Sort the languages according to the given order below List sortedLanguages = List.from(userProfile.languages); sortedLanguages.sort((a, b) { @@ -437,7 +435,8 @@ class UserMatchingPageState extends State { ), const SizedBox(height: 8), Center( - child: Text('${userProfile.name}$ageInfo', + child: Text( + '${userProfile.name} ${ageInfo(userProfile.born)}'.trim(), style: const TextStyle(fontSize: 24)), ), const SizedBox(height: 8), diff --git a/lib/pages/user_profile_page.dart b/lib/pages/user_profile_page.dart index f0db8e7..b717a4a 100644 --- a/lib/pages/user_profile_page.dart +++ b/lib/pages/user_profile_page.dart @@ -15,7 +15,9 @@ import 'user_data_page.dart'; import 'user_vision_page.dart'; class UserProfilePage extends StatefulWidget { - const UserProfilePage({super.key}); + const UserProfilePage({super.key, this.userId}); + + final String? userId; @override State createState() => _UserProfilePageState(); @@ -25,17 +27,23 @@ class _UserProfilePageState extends State { String? profileImageUrl; // Track the profile image URL late UserProfile myData; bool isLoading = true; + late bool isOwner; + late String _userId; @override void 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 _loadUserData(); } Future _loadUserData() async { - myData = await UserService.getUserProfileById( - FirebaseAuth.instance.currentUser!.uid); + myData = await UserService.getUserProfileById(_userId); setState(() { // Initialize the profile image URL @@ -193,7 +201,7 @@ class _UserProfilePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('My Profile Information'), + title: Text(isOwner ? 'My Profile Information' : 'Profile Information'), ), body: isLoading ? const Center(child: CircularProgressIndicator()) @@ -204,28 +212,34 @@ class _UserProfilePageState extends State { children: [ _buildAvatar(context), const SizedBox(height: 16), - Divider(color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 16), + if (isOwner) + Divider(color: Theme.of(context).colorScheme.primary), + if (isOwner) const SizedBox(height: 16), _buildLocation(context), const SizedBox(height: 16), - Divider(color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 16), + if (isOwner) + Divider(color: Theme.of(context).colorScheme.primary), + if (isOwner) const SizedBox(height: 16), _buildSkills(context), const SizedBox(height: 16), - Divider(color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 16), + if (isOwner) + Divider(color: Theme.of(context).colorScheme.primary), + if (isOwner) const SizedBox(height: 16), _buildVision(context), const SizedBox(height: 16), - Divider(color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 16), + if (isOwner) + Divider(color: Theme.of(context).colorScheme.primary), + if (isOwner) const SizedBox(height: 16), _buildWorkCulture(context), const SizedBox(height: 16), - Divider(color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 16), + if (isOwner) + Divider(color: Theme.of(context).colorScheme.primary), + if (isOwner) const SizedBox(height: 16), _buildRisks(context), const SizedBox(height: 16), - Divider(color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 16), + if (isOwner) + Divider(color: Theme.of(context).colorScheme.primary), + if (isOwner) const SizedBox(height: 16), ], ), ), @@ -259,17 +273,18 @@ class _UserProfilePageState extends State { ], ), ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Align( - alignment: Alignment.topRight, - child: OutlinedButton.icon( - label: const Text('Edit'), - icon: const Icon(Icons.edit), - onPressed: editUserCommunicationInfo, + if (isOwner) + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Align( + alignment: Alignment.topRight, + child: OutlinedButton.icon( + label: const Text('Edit'), + icon: const Icon(Icons.edit), + onPressed: editUserCommunicationInfo, + ), ), ), - ), ], ), const SizedBox(height: 16), @@ -326,17 +341,18 @@ class _UserProfilePageState extends State { ], ), ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Align( - alignment: Alignment.topRight, - child: OutlinedButton.icon( - label: const Text('Edit'), - icon: const Icon(Icons.edit), - onPressed: editUserWorkCultureInfo, + if (isOwner) + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Align( + alignment: Alignment.topRight, + child: OutlinedButton.icon( + label: const Text('Edit'), + icon: const Icon(Icons.edit), + onPressed: editUserWorkCultureInfo, + ), ), ), - ), ], ), const SizedBox(height: 16), @@ -396,14 +412,15 @@ class _UserProfilePageState extends State { ], ), ), - Align( - alignment: Alignment.topRight, - child: OutlinedButton.icon( - label: const Text('Edit'), - icon: const Icon(Icons.edit), - onPressed: editUserVisionInfo, + if (isOwner) + Align( + alignment: Alignment.topRight, + child: OutlinedButton.icon( + label: const Text('Edit'), + icon: const Icon(Icons.edit), + onPressed: editUserVisionInfo, + ), ), - ), ], ), const SizedBox(height: 16), @@ -463,14 +480,15 @@ class _UserProfilePageState extends State { ], ), ), - Align( - alignment: Alignment.topRight, - child: OutlinedButton.icon( - label: const Text('Edit'), - icon: const Icon(Icons.edit), - onPressed: editUserSkillsInfo, + if (isOwner) + Align( + alignment: Alignment.topRight, + child: OutlinedButton.icon( + label: const Text('Edit'), + icon: const Icon(Icons.edit), + onPressed: editUserSkillsInfo, + ), ), - ), ], ), const SizedBox(height: 16), @@ -495,14 +513,15 @@ class _UserProfilePageState extends State { ], ), ), - Align( - alignment: Alignment.topRight, - child: OutlinedButton.icon( - label: const Text('Edit'), - icon: const Icon(Icons.edit), - onPressed: editUserSkillsSoughtInfo, + if (isOwner) + Align( + alignment: Alignment.topRight, + child: OutlinedButton.icon( + label: const Text('Edit'), + icon: const Icon(Icons.edit), + onPressed: editUserSkillsSoughtInfo, + ), ), - ), ], ), ], @@ -521,44 +540,47 @@ class _UserProfilePageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Age', - style: TextStyle( - color: Theme.of(context).colorScheme.primary, + if (isOwner) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Age', + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), ), - ), - Text( - (age > 0 - ? '$age years old, born ${myData.born}' - : 'n/a'), - style: const TextStyle(fontSize: 16)), - ], - ), - Align( - alignment: Alignment.bottomRight, - child: OutlinedButton.icon( - label: const Text('Edit'), - icon: const Icon(Icons.edit), - onPressed: editUserDataInfo, + Text( + (age > 0 + ? '$age years old, born ${myData.born}' + : 'n/a'), + style: const TextStyle(fontSize: 16)), + ], ), - ), - ], - ), - const SizedBox(height: 16), - Text( - 'Gender', - style: TextStyle( - color: Theme.of(context).colorScheme.primary, + Align( + alignment: Alignment.bottomRight, + child: OutlinedButton.icon( + label: const Text('Edit'), + icon: const Icon(Icons.edit), + onPressed: editUserDataInfo, + ), + ), + ], ), - ), - Text(getDisplayText(myData.gender), - style: const TextStyle(fontSize: 16)), + if (isOwner) const SizedBox(height: 16), + if (isOwner) + 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), Text( 'Spoken languages', @@ -597,16 +619,27 @@ class _UserProfilePageState extends State { } 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( children: [ - Align( - alignment: Alignment.bottomRight, - child: OutlinedButton.icon( - label: const Text('Edit'), - icon: const Icon(Icons.edit), - onPressed: editNameInfo, + if (isOwner) + Align( + alignment: Alignment.bottomRight, + child: OutlinedButton.icon( + label: const Text('Edit'), + icon: const Icon(Icons.edit), + onPressed: editNameInfo, + ), ), - ), CircleAvatar( radius: 80, backgroundImage: @@ -618,8 +651,19 @@ class _UserProfilePageState extends State { : null, ), const SizedBox(height: 16), - Text(myData.name, style: const TextStyle(fontSize: 24)), - Text(myData.email, style: const TextStyle(fontSize: 16)), + Row( + 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), Align( alignment: Alignment.centerLeft, @@ -627,7 +671,7 @@ class _UserProfilePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Short description of yourself', + isOwner ? 'Short description of yourself' : 'Short description', style: TextStyle( color: Theme.of(context).colorScheme.primary, ), diff --git a/lib/utils/math.dart b/lib/utils/math.dart index d3be612..612977c 100644 --- a/lib/utils/math.dart +++ b/lib/utils/math.dart @@ -10,6 +10,14 @@ int calcAge(int? birthYear) { 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). /// @@ -45,8 +53,10 @@ double calculateDistance(double lat1, double lon1, double lat2, double lon2) { final dLon = _degreesToRadians(lon2 - lon1); final a = sin(dLat / 2) * sin(dLat / 2) + - cos(_degreesToRadians(lat1)) * cos(_degreesToRadians(lat2)) * - sin(dLon / 2) * sin(dLon / 2); + cos(_degreesToRadians(lat1)) * + cos(_degreesToRadians(lat2)) * + sin(dLon / 2) * + sin(dLon / 2); final c = 2 * atan2(sqrt(a), sqrt(1 - a)); return R * c;