Enhanced UserProfilePage to showcase either own or another user's profile
parent
98920a4e61
commit
8120ebda3d
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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<ChatPage> {
|
|||
@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<ChatPage> {
|
|||
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
|
||||
|
||||
// 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<String> 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<ChatPage> {
|
|||
),
|
||||
),
|
||||
ChatBubble(
|
||||
message: data['message'],
|
||||
message: data[Constants.dbFieldMessageText],
|
||||
isCurrentUser: isCurrentUser,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -78,6 +78,7 @@ class ConversationsPage extends StatelessWidget {
|
|||
builder: (context) => ChatPage(
|
||||
receiverEmail: userData[Constants.dbFieldUsersEmail],
|
||||
receiverID: userData[Constants.dbFieldUsersID],
|
||||
chatTitle: userData[Constants.dbFieldUsersName],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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<LikedUsersPage> {
|
|||
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>> likedOnlyUsers = [];
|
||||
List<MapEntry<Swipe, DocumentSnapshot>> matchedUsers = [];
|
||||
|
@ -376,7 +351,16 @@ class LikedUsersPageState extends State<LikedUsersPage> {
|
|||
}
|
||||
}, //_unlikeUser(user.id),
|
||||
onShowMatchMessage: _showMatchMessage,
|
||||
onViewInfo: () => _showUserInfo(user),
|
||||
onViewInfo: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => UserProfilePage(
|
||||
userId: user.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -257,6 +257,7 @@ class UserMatchingPageState extends State<UserMatchingPage> {
|
|||
builder: (context) => ChatPage(
|
||||
receiverEmail: swipedUser.email,
|
||||
receiverID: swipedUser.uid,
|
||||
chatTitle: swipedUser.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -390,9 +391,6 @@ class UserMatchingPageState extends State<UserMatchingPage> {
|
|||
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<Language> sortedLanguages = List.from(userProfile.languages);
|
||||
sortedLanguages.sort((a, b) {
|
||||
|
@ -437,7 +435,8 @@ class UserMatchingPageState extends State<UserMatchingPage> {
|
|||
),
|
||||
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),
|
||||
|
|
|
@ -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<UserProfilePage> createState() => _UserProfilePageState();
|
||||
|
@ -25,17 +27,23 @@ class _UserProfilePageState extends State<UserProfilePage> {
|
|||
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<void> _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<UserProfilePage> {
|
|||
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<UserProfilePage> {
|
|||
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<UserProfilePage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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<UserProfilePage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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<UserProfilePage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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<UserProfilePage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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<UserProfilePage> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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<UserProfilePage> {
|
|||
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<UserProfilePage> {
|
|||
}
|
||||
|
||||
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<UserProfilePage> {
|
|||
: 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<UserProfilePage> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Short description of yourself',
|
||||
isOwner ? 'Short description of yourself' : 'Short description',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue